package com.bestcem.xm.survey.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.BetweenFormatter;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HtmlUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bestcem.bp.xm.user.grpc.v1.services.GroupNode;
import com.bestcem.xm.common.core.domain.web.ServiceResult;
import com.bestcem.xm.common.core.exception.BusinessException;
import com.bestcem.xm.common.core.oss.enums.OssPathEnum;
import com.bestcem.xm.component.mybatis.service.impl.XmServiceImpl;
import com.bestcem.xm.component.security.context.SecurityContextHolder;
import com.bestcem.xm.component.security.dto.TokenDTO;
import com.bestcem.xm.componet.filestorage.service.StorageService;
import com.bestcem.xm.componet.redis.service.RedisService;
import com.bestcem.xm.qdes.grpc.v1.services.Project;
import com.bestcem.xm.qdes.grpc.v1.services.QStruct;
import com.bestcem.xm.survey.config.SurveyExecutorConfig;
import com.bestcem.xm.survey.controller.vo.SurveyColumnVo;
import com.bestcem.xm.survey.controller.vo.SurveyExportRecordVo;
import com.bestcem.xm.survey.convert.grpc.SurveyGrpcConvertMapper;
import com.bestcem.xm.survey.entity.*;
import com.bestcem.xm.survey.enums.ExportStatusEnum;
import com.bestcem.xm.survey.export.BaseTypeHandler;
import com.bestcem.xm.survey.grpc.client.DeliverGrpcClient;
import com.bestcem.xm.survey.grpc.client.MemberGrpcClient;
import com.bestcem.xm.survey.grpc.client.QdesGrpcClient;
import com.bestcem.xm.survey.grpc.client.UserGrpcClient;
import com.bestcem.xm.survey.grpc.client.dto.*;
import com.bestcem.xm.survey.grpc.v1.services.RespondentExportRecordStatus;
import com.bestcem.xm.survey.mapper.*;
import com.bestcem.xm.survey.service.*;
import com.bestcem.xm.survey.util.SurveyConstant;
import com.github.benmanes.caffeine.cache.Cache;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.math.NumberUtils;
import org.javatuples.Pair;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.bestcem.xm.common.core.constant.Constants.SEPARATOR;
import static com.bestcem.xm.common.core.uitls.DateUtil.DATETIME_PATTERN_FORTICKETNO;
import static com.bestcem.xm.survey.enums.ExtparamDtypeDataEnum.*;
import static com.bestcem.xm.survey.enums.QuestionDescTypeEnum.QUESTION_TYPE_TIMEDELTA;
import static com.bestcem.xm.survey.enums.QuestionDescTypeEnum.QUESTION_TYPE_TIMESTAMP;
import static com.bestcem.xm.survey.enums.QuestionTypeEnum.QUESTION_TYPE_AUTO_TABLE;
import static com.bestcem.xm.survey.enums.RespondentDataStatusEnum.RESPONDENT_DATA_STATUS_FORMAL;
import static com.bestcem.xm.survey.enums.RespondentStatusEnum.*;
import static com.bestcem.xm.survey.enums.TitleTypeEnum.*;
import static com.bestcem.xm.survey.grpc.v1.services.ColumnCtype.*;
import static com.bestcem.xm.survey.grpc.v1.services.ColumnQtype.*;
import static com.bestcem.xm.survey.grpc.v1.services.ColumnStatus.*;
import static com.bestcem.xm.survey.grpc.v1.services.RespondentDataStatus.RESPONDENT_DATA_STATUS_FORMAL_VALUE;
import static com.bestcem.xm.survey.grpc.v1.services.RespondentExportRecordFileType.*;
import static com.bestcem.xm.survey.grpc.v1.services.RespondentExportRecordStatus.*;
import static com.bestcem.xm.survey.util.SurveyConstant.*;

/**
 * @author guita
 * @description 针对表【ds_survey_respondentexportrecord】的数据库操作Service实现
 * @createDate 2023-01-09 15:15:37
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class SurveyRespondentExportRecordServiceImpl extends XmServiceImpl<SurveyRespondentExportRecordMapper, SurveyRespondentExportRecord> implements SurveyRespondentExportRecordService {

    private static final String ROOT_FILE_PATH = System.getProperty("user.dir");
    private @NonNull QdesGrpcClient qdesGrpcClient;
    private @NonNull UserGrpcClient userGrpcClient;
    private @NonNull DeliverGrpcClient deliverGrpcClient;

    private @NonNull SurveyRespondentMapper surveyRespondentMapper;
    private @NonNull SurveyRespondentDataMapper surveyRespondentDataMapper;
    private @NonNull SurveyColumnMapper surveyColumnMapper;
    private @NonNull SurveyRespondentWeixinMapper surveyRespondentWeixinMapper;
    private @NonNull SurveyRespondentAttachmentMapper surveyRespondentAttachmentMapper;
    private @NonNull SurveyRespondentPropDataMapper surveyRespondentPropDataMapper;
    private @NonNull SurveyGrpcConvertMapper surveyGrpcConvertMapper;
    private @NonNull SurveyRespondentModifyLogMapper surveyRespondentModifyLogMapper;


    private @NonNull RedisService redisService;
    private @NonNull SurveyCommonService surveyCommonService;
    private @NonNull SurveyColumnService surveyColumnService;
    private @NonNull SurveyExtParameterService surveyExtParameterService;
    private @NonNull SurveyMessageReceiverService surveyMessageReceiverService;

    //private @NonNull OssService ossService;
    private @NonNull List<BaseTypeHandler> baseTypeHandler;

    private @NonNull SurveyGrpcConvertMapper grpcConvertMapper;
    private @NonNull MemberGrpcClient memberGrpcClient;

    @Resource(name = "xmSurveyCache")
    private Cache<String, Object> xmSurveyCache;

    private @NonNull StorageService storageService;

    /**
     * 答题类型 和对应的实现类 对应关系
     */
    private final Map<String, BaseTypeHandler> handlerMap = new HashMap<>(36);

    @Resource(name = "executor")
    private Executor executor;

    /**
     * 初始化
     */
    @PostConstruct
    public void loadAllQuestionHandler() {
        for (BaseTypeHandler handler : baseTypeHandler) {
            handlerMap.put(handler.getQuestionTypeValue(), handler);
        }
    }


    private final Map<String, Function<SurveyRespondent, Map<String, Object>>> META_FRONT_FUNC = new HashMap<String, Function<SurveyRespondent, Map<String, Object>>>() {{
        put(COLUMNS_META_FRONT.get(0), (surveyRespondent) -> getRspdSeq(surveyRespondent));
        put(COLUMNS_META_FRONT.get(1), (surveyRespondent) -> getRspdStatus(surveyRespondent));
        put(COLUMNS_META_FRONT.get(2), (surveyRespondent) -> getDeliverSource(surveyRespondent));
        put(COLUMNS_META_FRONT.get(3), (surveyRespondent) -> getDeliverChannel(surveyRespondent));
        put(COLUMNS_META_FRONT.get(4), (surveyRespondent) -> getDeliverTime(surveyRespondent));
        put(COLUMNS_META_FRONT.get(5), (surveyRespondent) -> getRspdBeginTime(surveyRespondent));
        put(COLUMNS_META_FRONT.get(6), (surveyRespondent) -> getRspdFinishTime(surveyRespondent));
        put(COLUMNS_META_FRONT.get(7), (surveyRespondent) -> getRspdTakeTime(surveyRespondent));
        put(COLUMNS_META_FRONT.get(8), (surveyRespondent) -> getGroupName(surveyRespondent));
        put(COLUMNS_META_FRONT.get(9), (surveyRespondent) -> getGroupCode(surveyRespondent));
    }};

    private final Map<String, Function<Object, Map<String, Object>>> META_BEHIND_FUNC = new HashMap<String, Function<Object, Map<String, Object>>>() {{
        put(COLUMNS_META_BEHIND.get(0), (object) -> getClientTypeInfo((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(1), (object) -> getBrowser((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(2), (object) -> getOs((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(3), (object) -> getIp((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(4), (object) -> getIpCountry((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(5), (object) -> getIpProvince((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(6), (object) -> getIpCity((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(7), (object) -> getUserUniqueIdentification((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(8), (object) -> getWxChannel((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(9), (object) -> getWxNickname((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(10), (object) -> getWxAvatar((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(11), (object) -> getWxSex((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(12), (object) -> getWxMobile((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(13), (object) -> getWxEmail((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(14), (object) -> getWxCorpName((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(15), (object) -> getWxDepartment((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(16), (object) -> getWxPosition((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(17), (object) -> getWxCountry((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(18), (object) -> getWxProvince((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(19), (object) -> getWxCity((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(20), (object) -> getWxAppId((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(21), (object) -> getProjectCode((ProjectDto) object));
        put(COLUMNS_META_BEHIND.get(22), (object) -> getProjectTitle((ProjectDto) object));
        put(COLUMNS_META_BEHIND.get(23), (object) -> getVersion((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(24), (object) -> getDeliverId((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(25), (object) -> getRspdTakeTimeS((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(26), (object) -> getPersonId((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(27), (object) -> getDataStatus((SurveyRespondent) object));
        put(COLUMNS_META_BEHIND.get(28), (object) -> getIdySid((SurveyRespondent) object));
    }};

    private final Map<String, BiFunction<SurveyRespondent, MemberDto, Map<String, Object>>> META_MEMBER_FUNC = new HashMap<String, BiFunction<SurveyRespondent, MemberDto, Map<String, Object>>>() {{
        put(COLUMNS_MEMBER.get(0), (surveyRespondent, member) -> getMemberMemberNo(member));
        put(COLUMNS_MEMBER.get(1), (surveyRespondent, member) -> getMemberName(member));
        put(COLUMNS_MEMBER.get(2), (surveyRespondent, member) -> getMemberPhone(member));
        put(COLUMNS_MEMBER.get(3), (surveyRespondent, member) -> getMemberEmail(member));
        put(COLUMNS_MEMBER.get(4), (surveyRespondent, member) -> getMemberGender(member));
        put(COLUMNS_MEMBER.get(5), (surveyRespondent, member) -> getMemberBirthday(member));
        put(COLUMNS_MEMBER.get(6), (surveyRespondent, member) -> getMemberOpenId(surveyRespondent, member));
        put(COLUMNS_MEMBER.get(7), (surveyRespondent, member) -> getMemberAppId(surveyRespondent, member));
    }};

    private Map<String, Object> getMemberAppId(SurveyRespondent surveyRespondent, MemberDto member) {
        return toRowsObj(Objects.nonNull(surveyRespondent.getWxInfo()) && (StrUtil.isNotEmpty(member.getAppId())) ? surveyRespondent.getWxInfo().getAppId() : "");
    }

    private Map<String, Object> getMemberOpenId(SurveyRespondent surveyRespondent, MemberDto member) {
        return toRowsObj(Objects.nonNull(surveyRespondent.getWxInfo()) && (StrUtil.isNotEmpty(member.getOpenId())) ? surveyRespondent.getWxInfo().getOpenId() : "");
    }

    private Map<String, Object> getMemberBirthday(MemberDto member) {
        return toRowsObj(Objects.nonNull(member) && Objects.nonNull(member.getBirthday()) ? new SimpleDateFormat(DATETIME_PATTERN_FORTICKETNO).format(member.getBirthday()) : "");
    }

    private Map<String, Object> getMemberGender(MemberDto member) {
        return toRowsObj(Objects.nonNull(member) ? member.getGender() : "");
    }

    private Map<String, Object> getMemberEmail(MemberDto member) {
        return toRowsObj(Objects.nonNull(member) ? member.getEmail() : "");
    }

    private Map<String, Object> getMemberPhone(MemberDto member) {
        return toRowsObj(Objects.nonNull(member) ? member.getMobile() : "");
    }

    private Map<String, Object> getMemberName(MemberDto member) {
        return toRowsObj(Objects.nonNull(member) ? member.getName() : "");
    }

    private Map<String, Object> getMemberMemberNo(MemberDto member) {
        return toRowsObj(Objects.nonNull(member) ? member.getMemberNo() : "");
    }

    private Map<String, Object> getIdySid(SurveyRespondent surveyRespondent) {
        return toRowsObj(surveyRespondent.getIdySid());
    }

    private Map<String, Object> getDataStatus(SurveyRespondent surveyRespondent) {
        String data_str = RESPONDENT_DATA_STATUS_FORMAL.getValue().equals(surveyRespondent.getDataStatus()) ? "正式数据" : "测试数据";
        return toRowsObj(data_str);
    }

    private Map<String, Object> getPersonId(SurveyRespondent surveyRespondent) {
        return toRowsObj(surveyRespondent.getPersonId());
    }

    private Map<String, Object> getRspdTakeTimeS(SurveyRespondent surveyRespondent) {
        //返回答题时长秒
        if (Objects.nonNull(surveyRespondent.getFinishTime())) {
            return toRowsObj((Duration.between(surveyRespondent.getBeginTime(), surveyRespondent.getFinishTime()).getSeconds()));
        }
        return toRowsObj("");
    }

    private Map<String, Object> getDeliverId(SurveyRespondent surveyRespondent) {
        //获取投放编号
        return toRowsObj(surveyRespondent.getDeliverId());
    }

    private Map<String, Object> getVersion(SurveyRespondent project) {
        return toRowsObj(project.getVersion());
    }

    private Map<String, Object> getProjectTitle(ProjectDto project) {
        return toRowsObj(project.getTitle());
    }

    private Map<String, Object> getProjectCode(ProjectDto project) {
        return toRowsObj(project.getCode());
    }

    private Map<String, Object> getWxAppId(SurveyRespondent surveyRespondent) {
        if (Objects.isNull(surveyRespondent.getWxInfo())) {
            return toRowsObj("");
        }
        return toRowsObj(Optional.ofNullable(surveyRespondent.getWxInfo().getAppId()).orElse(""));
    }

    private Map<String, Object> getWxCity(SurveyRespondent surveyRespondent) {
        if (Objects.isNull(surveyRespondent.getWxInfo())) {
            return toRowsObj("");
        }
        return toRowsObj(Optional.ofNullable(surveyRespondent.getWxInfo().getWxCity()).orElse(""));
    }

    private Map<String, Object> getWxProvince(SurveyRespondent surveyRespondent) {
        if (Objects.isNull(surveyRespondent.getWxInfo())) {
            return toRowsObj("");
        }
        return toRowsObj(Optional.ofNullable(surveyRespondent.getWxInfo().getWxProvince()).orElse(""));
    }

    private Map<String, Object> getWxCountry(SurveyRespondent surveyRespondent) {
        if (Objects.isNull(surveyRespondent.getWxInfo())) {
            return toRowsObj("");
        }
        return toRowsObj(Optional.ofNullable(surveyRespondent.getWxInfo().getWxCountry()).orElse(""));
    }

    private Map<String, Object> getWxPosition(SurveyRespondent surveyRespondent) {
        if (Objects.isNull(surveyRespondent.getWxInfo())) {
            return toRowsObj("");
        }
        return toRowsObj(Optional.ofNullable(surveyRespondent.getWxInfo().getPosition()).orElse(""));
    }

    private Map<String, Object> getWxDepartment(SurveyRespondent surveyRespondent) {
        if (Objects.isNull(surveyRespondent.getWxInfo())) {
            return toRowsObj("");
        }
        String department = Optional.ofNullable(surveyRespondent.getWxInfo().getDepartment()).orElse("");
        if (StrUtil.isNotEmpty(department)) {
            try {
                //处理department, 将列表里的元素按空格隔开, 一般是没有数据，py处理通过json.loads(department)将department转换为对象
                if (CollUtil.isNotEmpty(JSONArray.parseArray(department, String.class))) {
                    department = StrUtil.join(" ", JSONArray.parseArray(department, String.class));
                }
            } catch (Exception e) {
                return toRowsObj("");
            }
        }
        return toRowsObj(department);
    }

    private Map<String, Object> getWxCorpName(SurveyRespondent surveyRespondent) {
        if (Objects.isNull(surveyRespondent.getWxInfo())) {
            return toRowsObj("");
        }
        return toRowsObj(Optional.ofNullable(surveyRespondent.getWxInfo().getCorpName()).orElse(""));
    }


    private Map<String, Object> getWxEmail(SurveyRespondent surveyRespondent) {
        if (Objects.isNull(surveyRespondent.getWxInfo())) {
            return toRowsObj("");
        }
        return toRowsObj(Optional.ofNullable(surveyRespondent.getWxInfo().getEmail()).orElse(""));
    }

    private Map<String, Object> getWxMobile(SurveyRespondent surveyRespondent) {
        if (Objects.isNull(surveyRespondent.getWxInfo())) {
            return toRowsObj("");
        }
        return toRowsObj(Optional.ofNullable(surveyRespondent.getWxInfo().getMobile()).orElse(""));
    }

    private Map<String, Object> getWxSex(SurveyRespondent surveyRespondent) {
        if (Objects.isNull(surveyRespondent.getWxInfo())) {
            return toRowsObj("");
        }
        Integer wxSex = surveyRespondent.getWxInfo().getWxSex();
        if (Objects.isNull(wxSex)) {
            return toRowsObj("");
        }
        //此处python做了wxSex不为int的处理
        return toRowsObj(WX_INFO_SEX_DICT.get(wxSex));
    }

    private Map<String, Object> getWxAvatar(SurveyRespondent surveyRespondent) {
        if (Objects.isNull(surveyRespondent.getWxInfo())) {
            return toRowsObj("");
        }
        return toRowsObj(Optional.ofNullable(surveyRespondent.getWxInfo().getAvatar()).orElse(""));
    }

    private Map<String, Object> getWxNickname(SurveyRespondent surveyRespondent) {
        if (Objects.isNull(surveyRespondent.getWxInfo())) {
            return toRowsObj("");
        }
        return toRowsObj(Optional.ofNullable(surveyRespondent.getWxInfo().getWxNickname()).orElse(""));
    }

    private Map<String, Object> getWxChannel(SurveyRespondent surveyRespondent) {
        if (Objects.isNull(surveyRespondent.getWxInfo())) {
            return toRowsObj("");
        }
        return toRowsObj(Optional.ofNullable(surveyRespondent.getWxInfo().getChannel()).orElse(""));
    }


    private Map<String, Object> getUserUniqueIdentification(SurveyRespondent surveyRespondent) {
        if (Objects.isNull(surveyRespondent.getWxInfo())) {
            return toRowsObj("");
        }
        return toRowsObj(Optional.ofNullable(surveyRespondent.getWxInfo().getOpenId()).orElse(""));
    }

    private Map<String, Object> getIpCity(SurveyRespondent surveyRespondent) {
        return toRowsObj(surveyRespondent.getIpCity());
    }

    private Map<String, Object> getIpProvince(SurveyRespondent surveyRespondent) {
        return toRowsObj(surveyRespondent.getIpProvince());
    }

    private Map<String, Object> getIpCountry(SurveyRespondent surveyRespondent) {
        return toRowsObj(surveyRespondent.getIpCountry());
    }

    private Map<String, Object> getIp(SurveyRespondent surveyRespondent) {
        return toRowsObj(surveyRespondent.getIp());
    }

    private Map<String, Object> getOs(SurveyRespondent surveyRespondent) {
        Map<String, String> data = parseUserAgent(surveyRespondent.getUserAgent());
        return toRowsObj(data.get("os"));
    }

    private Map<String, Object> getBrowser(SurveyRespondent surveyRespondent) {
        Map<String, String> data = parseUserAgent(surveyRespondent.getUserAgent());
        return toRowsObj(data.get("browser"));
    }

    /**
     * 解析浏览器User-Agent头信息
     *
     * @param userAgent User-Agent字符串
     * @return 浏览器信息(str)，操作系统信息(str)
     */
    @Override
    public Map<String, String> parseUserAgent(String userAgent) {
        UserAgent agent = UserAgentUtil.parse(userAgent);
//        JSONObject agent = JSONObject.parseObject(userAgent);
//        String browser = String.format("%s/%s", agent.getJSONObject("browser").getString("family"), agent.getJSONObject("browser").getString("version_string"));
//        String os = String.format("%s/%s", agent.getJSONObject("os").getString("family"), agent.getJSONObject("os").getString("version_string"));
        Map<String, String> data = new HashMap<>();
        data.put("browser", agent.getBrowser() + "/" + agent.getVersion());
        data.put("os", agent.getOs() + "/" + agent.getOsVersion());
        return data;
    }

    private Map<String, Object> getClientTypeInfo(SurveyRespondent surveyRespondent) {
        String client_type_info = RESPONDENT_CLIENT_TYPE_DICT.get(surveyRespondent.getClientType());
        //设备类型
        return toRowsObj(client_type_info);
    }

    private Map<String, Object> getGroupCode(SurveyRespondent surveyRespondent) {
        String groupId = surveyRespondent.getGroupId();
        if (StrUtil.isNotEmpty(groupId)) {
            GroupDto group = userGrpcClient.getGroup(groupId);
            if (Objects.nonNull(group)) {
                return toRowsObj(group.getCode());
            }
        }
        return toRowsObj("");
    }

    private Map<String, Object> getGroupName(SurveyRespondent surveyRespondent) {
        String groupId = surveyRespondent.getGroupId();
        if (StrUtil.isNotEmpty(groupId)) {
            GroupDto group = userGrpcClient.getGroup(groupId);
            if (Objects.nonNull(group)) {
                return toRowsObj(group.getTitle());
            }
        }
        return toRowsObj("全国");
    }

    private Map<String, Object> getRspdTakeTime(SurveyRespondent surveyRespondent) {
        if (Objects.nonNull(surveyRespondent.getFinishTime())) {
            //计算两者相差秒数
            return toRowsObj(secondsConvert2Hms(Duration.between(surveyRespondent.getBeginTime(), surveyRespondent.getFinishTime()).getSeconds()));
        }
        return toRowsObj("");
    }

    /**
     * 将秒转换成  xx时xx分xx秒
     *
     * @param seconds
     * @return
     */
    @Override
    public String secondsConvert2Hms(long seconds) {
        log.info("SurveyRespondentExportRecordServiceImpl secondsConvert2Hms 秒={}", seconds);
        return DateUtil.formatBetween(seconds * 1000, BetweenFormatter.Level.SECOND);
        //StringBuilder res = new StringBuilder();
        //long hour = seconds / 60 / 60;
        //long minutes = seconds / 60 % 60;
        //long remainingSeconds = seconds % 60;
        //if (hour > 0) {
        //    res.append(String.format("%s时", hour));
        //}
        //if (hour > 0 || minutes > 0) {
        //    res.append(String.format("%s分", minutes));
        //}
        //if (hour > 0 || minutes > 0 || remainingSeconds > 0) {
        //    res.append(String.format("%s时", remainingSeconds));
        //}
        //return res.toString();
    }

    private Map<String, Object> getRspdFinishTime(SurveyRespondent surveyRespondent) {
        return toRowsObj(Objects.nonNull(surveyRespondent.getFinishTime()) ? DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss").format(surveyRespondent.getFinishTime()) : "");
    }

    private Map<String, Object> getRspdBeginTime(SurveyRespondent surveyRespondent) {
        return toRowsObj(Objects.nonNull(surveyRespondent.getBeginTime()) ? DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss").format(surveyRespondent.getBeginTime()) : "");
    }

    private Map<String, Object> getDeliverTime(SurveyRespondent surveyRespondent) {
        String dlvrRid = surveyRespondent.getDlvrRid();
        if (StrUtil.isNotEmpty(dlvrRid)) {
            DeliverWechatmpRecordDto deliver_record = deliverGrpcClient.getWechatmprecord(dlvrRid);
            if (Objects.nonNull(deliver_record) && Objects.nonNull(deliver_record.getPushTime())) {
                return toRowsObj(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss").format(deliver_record.getPushTime()));
            }
        } else if (StrUtil.isNotEmpty(surveyRespondent.getDeliverId())) {
            DeliverDto deliver = deliverGrpcClient.getDeliver(surveyRespondent.getDeliverId());
            if (Objects.nonNull(deliver) && Objects.nonNull(deliver.getDeliverTime())) {
                return toRowsObj(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss").format(deliver.getDeliverTime()));
            }
        }
        return toRowsObj("");
    }

    /**
     * 获取投放渠道
     *
     * @param surveyRespondent
     * @return
     */
    private Map<String, Object> getDeliverChannel(SurveyRespondent surveyRespondent) {
        DeliverDto deliver = deliverGrpcClient.getDeliver(surveyRespondent.getDeliverId());
        Object deliver_channel = Objects.nonNull(deliver) ? surveyColumnService.getdeliverChannelName(deliver.getChannel().toString()) : "";
        return toRowsObj(deliver_channel);
    }


    private Map<String, Object> getDeliverSource(SurveyRespondent surveyRespondent) {
        if (surveyRespondent == null || StrUtil.isBlank(surveyRespondent.getDeliverId())) {
            return toRowsObj("");
        }
        DeliverDto deliver = deliverGrpcClient.getDeliver(surveyRespondent.getDeliverId());
        String deliver_source = Objects.nonNull(deliver) ? deliver.getName() : "";
        return toRowsObj(deliver_source);
    }

    private Map<String, Object> getRspdStatus(SurveyRespondent surveyRespondent) {
        return toRowsObj(RESPONDENT_STATUS_DICT.getOrDefault(surveyRespondent.getStatus(), ""));
    }

    public Map<String, Object> getRspdSeq(SurveyRespondent surveyRespondent) {
        return toRowsObj(surveyRespondent.getSeq());
    }

    @Override
    public SurveyRespondentExportRecord getOneRespondentexportrecord(String id) {
        SurveyRespondentExportRecord surveyRespondentExportRecord = baseMapper.selectById(id);
        if (ObjectUtil.isNull(surveyRespondentExportRecord)) {
            throw new BusinessException("RespondentExportRecord[id={" + id + "}] not found");
        }
        return surveyRespondentExportRecord;
    }

    @Override
    public List<SurveyRespondentExportRecord> getRespondentExportRecordList(JSONObject args) {
        LambdaQueryWrapper<SurveyRespondentExportRecord> queryWrapper = new LambdaQueryWrapper<>();
        if (ObjectUtil.isNotEmpty(args.get("_id"))) {
            queryWrapper.eq(SurveyRespondentExportRecord::getId, args.get("_id"));
        }
        if (ObjectUtil.isNotEmpty(args.get("created_id"))) {
            if (args.get("created_id") instanceof Collection) {
                queryWrapper.in(SurveyRespondentExportRecord::getCreatedId, args.getJSONArray("created_id"));
            } else {
                queryWrapper.eq(SurveyRespondentExportRecord::getCreatedId, args.get("created_id"));
            }
        }
        if (ObjectUtil.isNotEmpty(args.get("file_type"))) {
            if (args.get("file_type") instanceof Collection) {
                queryWrapper.in(SurveyRespondentExportRecord::getFileType, args.getJSONArray("file_type"));
            } else {
                queryWrapper.eq(SurveyRespondentExportRecord::getFileType, args.get("file_type"));
            }
        }
        if (ObjectUtil.isNotEmpty(args.get("status"))) {
            if (args.get("status") instanceof Collection) {
                queryWrapper.in(SurveyRespondentExportRecord::getStatus, args.getJSONArray("status"));
            } else {
                queryWrapper.eq(SurveyRespondentExportRecord::getStatus, args.get("status"));
            }
        }
        if (ObjectUtil.isNotEmpty(args.get("search"))) {
            //后续条件补充
        }
        return baseMapper.selectList(queryWrapper);
    }

    @Override
    public SurveyExportRecordVo getExportRecordPage(Page<SurveyRespondentExportRecord> page, JSONObject args) {
        SurveyExportRecordVo pageVo = new SurveyExportRecordVo();
        pageVo.setPage(page.getCurrent());

        LambdaQueryWrapper<SurveyRespondentExportRecord> queryWrapper = new LambdaQueryWrapper<>();

        if (ObjectUtil.isNotEmpty(args.get("_id"))) {
            queryWrapper.eq(SurveyRespondentExportRecord::getId, args.get("_id"));
        }
        if (ObjectUtil.isNotEmpty(args.get("created_id"))) {
            if (args.get("created_id") instanceof Collection) {
                queryWrapper.in(SurveyRespondentExportRecord::getCreatedId, args.getJSONArray("created_id"));
            } else {
                queryWrapper.eq(SurveyRespondentExportRecord::getCreatedId, args.get("created_id"));
            }
        }
        if (ObjectUtil.isNotEmpty(args.get("file_type"))) {
            if (args.get("file_type") instanceof Collection) {
                queryWrapper.in(SurveyRespondentExportRecord::getFileType, args.getJSONArray("file_type"));
            } else {
                queryWrapper.eq(SurveyRespondentExportRecord::getFileType, args.get("file_type"));
            }
        }
        if (ObjectUtil.isNotEmpty(args.get("status"))) {
            if (args.get("status") instanceof Collection) {
                queryWrapper.in(SurveyRespondentExportRecord::getStatus, args.getJSONArray("status"));
            } else {
                queryWrapper.eq(SurveyRespondentExportRecord::getStatus, args.get("status"));
            }
        }
        if (ObjectUtil.isNotEmpty(args.get("search"))) {
            //后续条件补充
        }
        //前段显示需要按照创建时间倒序
        queryWrapper.orderByDesc(SurveyRespondentExportRecord::getCreatedDt);

        Page<SurveyRespondentExportRecord> surveyRespondentExportRecordPage = baseMapper.selectPage(page, queryWrapper);
        List<SurveyRespondentExportRecord> exportRecordList = surveyRespondentExportRecordPage.getRecords();
        if (CollUtil.isEmpty(exportRecordList)) {
            pageVo.setTotalCount(0);
            pageVo.setRows(new ArrayList<>());
            return pageVo;
        }
        //组装
        pageVo.setRows(exportRecordList);
        pageVo.setTotalCount(surveyRespondentExportRecordPage.getTotal());
        return pageVo;
    }

    @Override
    public Map<String, Integer> deleteOneRespondentexportrecord(String id) {
        int deleteCount = baseMapper.deleteById(id);
        if (deleteCount == 0) {
            throw new BusinessException("RespondentExportRecord[id={" + id + "}] not found");
        }
        Map<String, Integer> map = new HashMap<>();
        map.put("deletedCount", deleteCount);
        return map;
    }

    @Override
    public Map<String, Integer> deleteRespondentexportrecordList(JSONObject args) {
        Map<String, Integer> map = new HashMap<>();
        if (ObjectUtil.isNotEmpty(args.get("idList"))) {
            if (args.get("idList") instanceof Collection) {
                int deleteCount = baseMapper.deleteBatchIds(args.getJSONArray("idList"));
                map.put("deletedCount", deleteCount);
            } else {
                throw new BusinessException("idList must be list");
            }
        } else {
            throw new BusinessException("idList is required");
        }
        return map;
    }

    @Override
    public ServiceResult<Map<String, String>> respondentsV12Excel(String id, JSONObject param) {
        ProjectDto project = grpcConvertMapper.toProjectDto(qdesGrpcClient.getProject(id));
        if (Objects.isNull(project)) {
            throw new BusinessException("Project[id={" + id + "}] not found");
        }
        if (project.getStatus().equals(QDES_PROJECT_STATUS_CREATED)) {
            throw new BusinessException("问卷未发布");
        }
        //开放平台请求时加上此参数，便于获取导出记录id
        String source = param.getString("request_source");
        if (StrUtil.isNotEmpty(source) && source.startsWith("self:")) {
            param.put("request_source", source.replaceAll("self:", ""));
        }
        String orgId = SecurityContextHolder.getOrgId();
        String userId = SecurityContextHolder.getUserId();
        checkOrg(project.getCompanyId(), orgId);
        //String sys_id = getSystemIdByCode(orgName, "survey");
        Map<String, String> filemap = new HashMap<>();
        filemap.put("sysID", "xm");
        filemap.put("projectID", id);
        String dtype = (String) param.getOrDefault("dtype", "01");
        /*String language_code = param.getString("language_code");
        String translation_language_id;
        if (StrUtil.isNotEmpty(language_code) && !language_code.equals(project.getAnswerLanguage())) {
            List<TranslationLanguage> translation_language = listTranslationlanguage(id, language_code);
            if (!CollectionUtils.isEmpty(translation_language)) {
                translation_language_id = translation_language.get(0).getId();
            } else {
                throw new BusinessException("translationlanguage code = {" + language_code + "}]not found");
            }
        } else {
            language_code = project.getAnswerLanguage();
            translation_language_id = null;
        }*/
        Boolean score_apply_type = (Boolean) param.getOrDefault("not_apply", false);
        String name = null;
        String filepath = null;
        if (SurveyConstant.D_TYPE.contains(dtype)) {
            if ("spss_data".equals(dtype)) {
                filemap.put("fileName", project.getTitle() + "_" + RESPONDENT_EXPORT_MAP.getOrDefault(dtype, dtype) + "_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + ".sav");
            } else {
                filemap.put("fileName", project.getTitle() + "_" + RESPONDENT_EXPORT_MAP.getOrDefault(dtype, dtype) + "_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
            }
            genRspdData(project, dtype, project.getReleaseVer(), filemap, score_apply_type, userId, param);
            return ServiceResult.success();
        } else if ("01_spss_label".equals(dtype) || "not01_spss_label".equals(dtype)) {
            boolean not01 = "not01_spss_label".equals(dtype);
            boolean need_title;
            if (ObjectUtil.isNotEmpty(param.get("not_need_title"))) {
                need_title = false;
                name = not01 ? String.format("%s_spss_labels_非01编码(多选题选项不带题干)_%s.txt", project.getTitle(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))) : String.format("%s_spss_labels_01编码(多选题选项不带题干)_%s.txt", project.getTitle(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
            } else {
                need_title = true;
                name = not01 ? String.format("%s_spss_labels_非01编码_(多选题选项不带题干)_%s.txt", project.getTitle(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))) : String.format("%s_spss_labels_01编码_%s.txt", project.getTitle(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
            }
            filemap.put("fileName", name);
            filepath = genSpssLabelsData(param, project, dtype, not01, need_title, filemap, userId);
        } else if ("01_multicode".equals(dtype) || "not01_multicode".equals(dtype)) {
            boolean not01 = false;
            if ("not01_multicode".equals(dtype)) {
                not01 = true;
                name = String.format("%s_spss_labels_非01编码_%s.txt", project.getTitle(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
            } else {
                name = String.format("%s_spss_labels_01编码_%s.txt", project.getTitle(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
            }
            filemap.put("fileName", name);
            filepath = genMulticodeData(project, dtype, not01, filemap, userId, param);
        } else if ("spss_code".equals(dtype)) {
            name = String.format("%s_交叉表框架代码_%s.txt", project.getTitle(), LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
            filemap.put("fileName", name);
            filepath = genSpssCode(project, dtype, filemap, userId, param);
        }
        Map<String, String> res = new HashMap<>();
        res.put("name", name);
        res.put("filepath", filepath);
        return ServiceResult.success(res);
    }

    private String genSpssCode(ProjectDto project, String dtype, Map filemap, String userId, JSONObject param) {
        // 判断是否有正在导出的任务,并设置锁
        if (!setExportTaskLock(project.getId(), dtype, userId)) {
            throw new BusinessException("当前已有导出答卷数据任务正在执行中，请稍后再试");
        }
        //创建导出记录
        String exportRecord = createExportRecord(project.getId(), dtype, filemap, userId, param);
        //生成spss数据
        return asyncExportSpssCode(project.getId(), project.getReleaseVer(), filemap, exportRecord, dtype, param);
    }

    private String asyncExportSpssCode(String projectId, Integer version, Map filemap, String record_id, String dtype, JSONObject param) {
        //spss labels卷数据
        List<Integer> versions = new ArrayList<>(version);
        for (int i = 1; i < version + 1; i++) {
            versions.add(i);
        }
        List<QstructsDto> qstruct_list = qdesGrpcClient.qdesListQstructWithq(projectId, version).stream().map(grpcConvertMapper::toQstructsDto).collect(Collectors.toList());
        if (qstruct_list.isEmpty()) {
            throw new BusinessException("cannot find qstruct");
        }
        int total_count = qstruct_list.size();
        String data = genSpssData(qstruct_list, record_id, total_count, dtype, param);
        //String file_path = ossService.getCdn() + ossService.getFilePrefix() + "/download/surveyRespondent/" + filemap.get("fileName");
        //String ossPath = OssPathEnum.TEMP_SURVEY.getPath() + SURVEY_EXPORT + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN) + SEPARATOR;
        //String file_url = ossService.uploadFile(data.getBytes(), (String) filemap.get("fileName"), OssPathEnum.TEMP_SURVEY, null);
        String file_url = storageService.upload(data.getBytes(), (String) filemap.get("fileName"), OssPathEnum.TEMP_SURVEY.getPath(), new Dict()).getUrl();
        SurveyRespondentExportRecord exportRecord = new SurveyRespondentExportRecord();
        exportRecord.setId(record_id);
        exportRecord.setStatus(RESPONDENTEXPORTRECORD_STATUS_SUCESS.getNumber());
        exportRecord.setFileSize(String.format("%.2f", data.getBytes().length / 1024.0) + "k");
        exportRecord.setFilePath(file_url);
        baseMapper.updateById(exportRecord);
        //文件保存成功后在把进度设置成100%，设置进度条结束值
        setProgressBarStatus(SURVEY_RESPONDENT_EXPORT, record_id, total_count, total_count, 3700);
        return file_url;
    }

    private String genSpssData(List<QstructsDto> qstruct_list, String record_id, int total_count, String dtype, JSONObject param) {
        int per = calcUpdateProgressStep(total_count);
        //设置进度条初始值
        setProgressBarStatus(SURVEY_RESPONDENT_EXPORT, record_id, 0, total_count, 3700);
        qstruct_list = qstruct_list.stream().sorted(Comparator.comparing(QstructsDto::getVersion)).collect(Collectors.toList());
        List<String> head_list = new ArrayList();
        Set gidset = new HashSet();
        int row = 0;

        QstructsDto qstruct = qstruct_list.get(0);
        List<JSONObject> question_list = new ArrayList<>();
        JSONObject object = JSONObject.parseObject(qstruct.getData());
        getQstructQuestions(object, new HashMap<>(), question_list);
        question_list.forEach(question -> {
            if (!gidset.contains(question.getString("gid"))) {
                BaseTypeHandler handler = handlerMap.get(dtype);
                if (Objects.nonNull(handler)) {
                    List<String> val_ext = handler.getSpssCodeV2(param, qstruct);
                    head_list.addAll(val_ext);
                }
                gidset.add(question.getString("gid"));
            }
        });
        //每per条更新一次进度
        if (row % per == 0) {
            setProgressBarStatus(SURVEY_RESPONDENT_EXPORT, record_id, row, total_count, 3700);
        }
        row += 1;
        // 产品要求这版只返回最新版本的问卷信息，但是之后还会考虑老版本，目前先加break
        String head_str = StrUtil.join(" ", head_list);
        List<String> finals = new ArrayList();
        appendHead(finals);
        finals.add(head_str);
        appendFloot(finals);

        String content = StrUtil.join("", finals);
        try {
            return java.net.URLEncoder.encode(content, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            log.error("编码异常:", e);
            throw new BusinessException("编码异常：" + e.getMessage());
        }
    }

    private void appendFloot(List<String> finals) {
        finals.add(" BY status [COUNT F40.0, COLPCT.COUNT PCT40.1]\n");
        finals.add("/CATEGORIES VARIABLES=All EMPTY=INCLUDE TOTAL=YES POSITION=BEFORE\n");
        finals.add("/CATEGORIES VARIABLES=status KEY=VALUE EMPTY=EXCLUDE TOTAL=YES\n");
        finals.add("POSITION=BEFORE.\n");
    }

    private void appendHead(List<String> finals) {
        finals.add("*转换变量类型.\n");
        finals.add("VARIABLE LEVEL all(nominal).\n");
        finals.add("* Custom Tables.\n");
        finals.add("CTABLES\n");
        finals.add("/FORMAT EMPTY='-' MISSING='.'\n");
        finals.add("/SMISSING VARIABLE\n");
        finals.add("/VLABELS VARIABLES=ALL DISPLAY=DEFAULT\n");
        finals.add("/TABLE ");
    }


    private String genMulticodeData(ProjectDto project, String dtype, boolean not01, Map filemap, String userId, JSONObject param) {
        // 判断是否有正在导出的任务,并设置锁
        if (!setExportTaskLock(project.getId(), dtype, userId)) {
            throw new BusinessException("当前已有导出答卷数据任务正在执行中，请稍后再试");
        }
        //创建导出记录
        String exportRecord = createExportRecord(project.getId(), dtype, filemap, userId, param);
        //生成spss数据
        return asyncExportMulticodeData(project.getId(), project.getReleaseVer(), filemap, exportRecord, not01);
    }

    private String asyncExportMulticodeData(String projectId, Integer version, Map filemap, String record_id, boolean not01) {
        //spss labels卷数据
        List<Integer> versions = new ArrayList<>(version);
        for (int i = 1; i < version + 1; i++) {
            versions.add(i);
        }
        List<QstructsDto> qstruct_list = qdesGrpcClient.qdesListQstructWithq(projectId, versions).stream().map(grpcConvertMapper::toQstructsDto).collect(Collectors.toList());
        if (qstruct_list.isEmpty()) {
            throw new BusinessException("cannot find qstruct");
        }
        int total_count = qstruct_list.size();
        String data = genMulticode(qstruct_list, not01, record_id, total_count);
        //String file_path = ossService.getCdn() + ossService.getFilePrefix() + "/download/surveyRespondent/" + filemap.get("fileName");
        //String file_url = ossService.uploadFile(data.getBytes(), (String) filemap.get("fileName"), OssPathEnum.TEMP_SURVEY, null);
        String file_url = storageService.upload(data.getBytes(), (String) filemap.get("fileName"), OssPathEnum.TEMP_SURVEY.getPath(), new Dict()).getUrl();
        SurveyRespondentExportRecord exportRecord = new SurveyRespondentExportRecord();
        exportRecord.setId(record_id);
        exportRecord.setStatus(RESPONDENTEXPORTRECORD_STATUS_SUCESS.getNumber());
        exportRecord.setFileSize(String.format("%.2f", data.getBytes().length / 1024.0) + "k");
        exportRecord.setFilePath(file_url);
        baseMapper.updateById(exportRecord);
        //文件保存成功后在把进度设置成100%
        setProgressBarStatus(SURVEY_RESPONDENT_EXPORT, record_id, total_count, total_count, 3700);
        log.debug("export multicode_data data {}", file_url);
        return file_url;
    }

    private String genMulticode(List<QstructsDto> qstruct_list, boolean not01, String record_id, int total_count) {
        int per = calcUpdateProgressStep(total_count);
        //设置进度条初始值
        setProgressBarStatus(SURVEY_RESPONDENT_EXPORT, record_id, 0, total_count, 3700);
        qstruct_list = qstruct_list.stream().sorted(Comparator.comparing(QstructsDto::getVersion)).collect(Collectors.toList());
        List<String> value_list = new ArrayList<>();
        value_list.add("MRSETS");
        List<String> qcid_list = new ArrayList<>();
        Set gidset = new HashSet();
        int row = 0;
        QstructsDto qstruct = qstruct_list.get(0);
        List<JSONObject> question_list = new ArrayList<>();
        JSONObject object = JSONObject.parseObject(qstruct.getData());
        getQstructQuestions(object, new HashMap<>(), question_list);
        question_list.forEach(question -> {
            if (!gidset.contains(question.getString("gid"))) {
                gidset.add(question.getString("gid"));
                String q_cid = question.getString("cid");
                String qtype = question.getString("qtype");
                List<JSONObject> matrixrow_list = (List) question.getOrDefault("rows_items", Collections.emptyList());
                List<JSONObject> option_list = (List) question.getOrDefault("items", Collections.emptyList());
                if (MATRIX_MULTIPLE.getName().equals(qtype)) {
                    matrixrow_list.forEach(matrixrow -> {
                        String mcid = matrixrow.getString("oid");
                        String head = String.format("%s__%s", q_cid, mcid);
                        qcid_list.add(String.format("$%s", head));
                        List data_head_list = new ArrayList();
                        option_list.forEach(option -> data_head_list.add(String.format("'%s__%s__%s", q_cid, mcid, option.getString("oid"))));
                        if (CollUtil.isNotEmpty(data_head_list)) {
                            String s = not01 ? String.format("/MCGROUP NAME=$%s LABEL='%s %s' VARIABLES=%s to %s", head, BaseTypeHandler.stripHtmlWarp(question.getString("title")), BaseTypeHandler.stripHtmlWarp(matrixrow.getString("title")), data_head_list.get(0), data_head_list.get(data_head_list.size() - 1)) : String.format("/MDGROUP NAME=$%s LABEL='%s %s' CATEGORYLABELS=VARLABELS VARIABLES=%s to %s VALUE=1", head, BaseTypeHandler.stripHtmlWarp(question.getString("title")), BaseTypeHandler.stripHtmlWarp(matrixrow.getString("title")), data_head_list.get(0), data_head_list.get(data_head_list.size() - 1));
                            appendValue(s, value_list);
                        }
                    });
                } else if (MULTIPLE.getName().equals(qtype)) {
                    qcid_list.add(String.format("$%s", q_cid));
                    List<String> data_head_list = option_list.stream().map(v -> String.format("%s__%s", q_cid, v.getString("oid"))).collect(Collectors.toList());
                    String s = not01 ? String.format("/MCGROUP NAME=$%s LABEL='%s' VARIABLES=%s to %s", q_cid, BaseTypeHandler.stripHtmlWarp(question.getString("title")), data_head_list.get(0), data_head_list.get(data_head_list.size() - 1)) : String.format("/MDGROUP NAME=$%s LABEL='%s' CATEGORYLABELS=VARLABELS VARIABLES=%s to %s VALUE=1", q_cid, BaseTypeHandler.stripHtmlWarp(question.getString("title")), data_head_list.get(0), data_head_list.get(data_head_list.size() - 1));
                    appendValue(s, value_list);
                } else if (EVALUATION.getName().equals(qtype)) {
                    //旧版本评价题在多重里面不显示
                    List<JSONObject> tag_list = (List<JSONObject>) ((JSONObject) question.getOrDefault("current_template", Collections.emptyMap())).get("tagList");
                    if (CollUtil.isNotEmpty(tag_list)) {
                        String head = String.format("%s__tag", q_cid);
                        qcid_list.add(String.format("$%s", head));
                        List<JSONObject> sorted_tag_list = getSortedTagList(question);
                        List<String> data_tag_list = sorted_tag_list.stream().map(v -> String.format("%s__%s", q_cid, v.get("oid"))).collect(Collectors.toList());
                        if (CollUtil.isNotEmpty(data_tag_list)) {
                            String s = not01 ? String.format("/MCGROUP NAME=$%s LABEL='%s' VARIABLES=%s to %s", q_cid, BaseTypeHandler.stripHtmlWarp(question.getString("title")), data_tag_list.get(0), data_tag_list.get(data_tag_list.size() - 1)) : String.format("/MDGROUP NAME=$%s LABEL='%s' CATEGORYLABELS=VARLABELS VARIABLES=%s to %s VALUE=1", q_cid, BaseTypeHandler.stripHtmlWarp(question.getString("title")), data_tag_list.get(0), data_tag_list.get(data_tag_list.size() - 1));
                            appendValue(s, value_list);
                        }
                    }

                }
            }
        });
        //每per条更新一次进度
        if (row % per == 0) {
            setProgressBarStatus(SURVEY_RESPONDENT_EXPORT, record_id, row, total_count, 3700);
        }
        row += 1;
        // 产品要求这版只返回最新版本的问卷信息，但是之后还会考虑老版本，目前先加break
        String qcid_str = StrUtil.join(" ", qcid_list);
        String s = String.format("/DISPLAY NAME=[%s].", qcid_str);
        appendValue(s, value_list);
        String content = StrUtil.join("\r\n", value_list);
        try {
            return java.net.URLEncoder.encode(content, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            log.error("编码异常：", e);
            throw new BusinessException("编码异常：" + e.getMessage());
        }
    }

    private void appendValue(String s, List<String> value_list) {
        List<String> line_list = BaseTypeHandler.convertString(s, 250);
        value_list.addAll(line_list);
    }

    private String genSpssLabelsData(JSONObject param, ProjectDto project, String dtype, boolean not01, boolean need_title, Map filemap, String userId) {
        // 判断是否有正在导出的任务,并设置锁
        if (!setExportTaskLock(project.getId(), dtype, userId)) {
            throw new BusinessException("当前已有导出答卷数据任务正在执行中，请稍后再试");
        }
        //创建导出记录
        String exportRecord = createExportRecord(project.getId(), dtype, filemap, userId, param);
        //生成spss数据
        return asyncExportSpssLabelsData(project.getId(), project.getReleaseVer(), filemap, exportRecord, not01, need_title, dtype, userId);
    }

    private String asyncExportSpssLabelsData(String projectId, Integer version, Map filemap, String record_id, boolean not01, boolean need_title, String dtype, String userId) {
        //spss labels卷数据
        List<Integer> versions = new ArrayList<>(version);
        for (int i = 1; i < version + 1; i++) {
            versions.add(i);
        }
        List<QstructsDto> qstruct_list = qdesGrpcClient.qdesListQstructWithq(projectId, version).stream().map(grpcConvertMapper::toQstructsDto).collect(Collectors.toList());
        if (qstruct_list.isEmpty()) {
            throw new BusinessException("cannot find qstruct");
        }
        int total_count = qstruct_list.size();
        String data = genSpssLabels(qstruct_list, not01, need_title, record_id, total_count, dtype);
        //String file_path = ossService.getCdn() + ossService.getFilePrefix() + "/download/surveyRespondent/" + filemap.get("fileName");
        //String ossPath = OssPathEnum.TEMP_SURVEY.getPath() + SURVEY_EXPORT + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN) + SEPARATOR;
        //String file_url = ossService.uploadFile(data.getBytes(), (String) filemap.get("fileName"), OssPathEnum.TEMP_SURVEY, null);
        String file_url = storageService.upload(data.getBytes(), (String) filemap.get("fileName"), OssPathEnum.TEMP_SURVEY.getPath(), new Dict()).getUrl();
        SurveyRespondentExportRecord exportRecord = new SurveyRespondentExportRecord();
        exportRecord.setId(record_id);
        exportRecord.setStatus(RESPONDENTEXPORTRECORD_STATUS_SUCESS.getNumber());
        exportRecord.setFileSize(String.format("%.2f", data.getBytes().length / 1024.0) + "k");
        exportRecord.setFilePath(file_url);
        baseMapper.updateById(exportRecord);
        //文件保存成功后在把进度设置成100%
        setProgressBarStatus(SURVEY_RESPONDENT_EXPORT, record_id, total_count, total_count, 3700);
        log.debug("export spss_lables data {}", file_url);
        return file_url;
    }

    private String genSpssLabels(List<QstructsDto> qstruct_list, boolean not01, boolean need_title, String record_id, int total_count, String dtype) {
        int per = calcUpdateProgressStep(total_count);
        //设置进度条初始值
        setProgressBarStatus(SURVEY_RESPONDENT_EXPORT, record_id, 0, total_count, 3700);
        qstruct_list = qstruct_list.stream().sorted(Comparator.comparing(QstructsDto::getVersion)).collect(Collectors.toList());
        List<String> var_list = new ArrayList();
        List<String> value_list = new ArrayList();
        Set gids = new HashSet();
        int row = 0;
        QstructsDto qstruct = qstruct_list.get(0);
        List<JSONObject> question_list = new ArrayList<>();
        JSONObject object = JSONObject.parseObject(qstruct.getData());
        getQstructQuestions(object, new HashMap<>(), question_list);
        question_list.forEach(q -> {
            if (!gids.contains(q.getString("gid"))) {
                BaseTypeHandler typeHandler = handlerMap.get(dtype);
                List<List<String>> varLabelsV2List = typeHandler.getVarLabelsV2(object, not01, need_title);
                var_list.addAll(varLabelsV2List.get(0));
                value_list.addAll(varLabelsV2List.get(1));
                gids.add(q.getString("gid"));
            }
        });
        //每per条更新一次进度
        if (row % per == 0) {
            setProgressBarStatus(SURVEY_RESPONDENT_EXPORT, record_id, row, total_count, 3700);
        }
        row += 1;
        // 产品要求这版只返回最新版本的问卷信息，但是之后还会考虑老版本，目前先加break
        List<String> data_list = new ArrayList<>();
        data_list.addAll(var_list);
        data_list.addAll(value_list);
        String content = StrUtil.join("\r\n", data_list);
        try {
            return java.net.URLEncoder.encode(content, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            log.error("编码异常:", e);
            throw new BusinessException("编码异常：" + e.getMessage());
        }
    }

    private void genRspdData(ProjectDto project, String dtype, int releaseVer, Map filemap, Boolean score_apply_type, String userId, JSONObject param) {
        String pid = project.getId();
        LambdaQueryWrapper<SurveyRespondent> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SurveyRespondent::getProjectId, pid);
        queryWrapper.last("limit 1");
        SurveyRespondent surveyRespondent = surveyRespondentMapper.selectOne(queryWrapper);
        if (Objects.isNull(surveyRespondent)) {
            throw new BusinessException("no data");
        }
        //判断是否有正在导出的任务,并设置锁
        if (!setExportTaskLock(pid, dtype, userId)) {
            throw new BusinessException("当前已有导出答卷数据任务正在执行中，请稍后再试");
        }
        String record_id = createExportRecord(pid, dtype, filemap, userId, param);
        SurveyRespondentExportRecord exportRecord = new SurveyRespondentExportRecord();
        exportRecord.setId(record_id);
        /*try {
            //异步生成答卷数据
            //CompletableFuture.runAsync(() -> asyncExportRspdData(project, param, dtype, releaseVer, userId, record_id, filemap, score_apply_type), executor);
            CompletableFuture.runAsync(() -> {
                try {
                    asyncExportRspdData(project, param, dtype, releaseVer, userId, record_id, filemap, score_apply_type);
                } catch (Exception e) {
                    log.error("生成答卷数据异常:", e);
                    throw new BusinessException("生成答卷数据异常", e);
                }
            });
            //导出记录中增加task_id
            String task_id = UUID.randomUUID().toString();
            exportRecord.setTaskId(task_id);
        } catch (Exception e) {
            deleteExportTaskLock(pid, dtype, SecurityContextHolder.getUserId());
            log.error("创建导出答卷任务失败. error:", e);
            exportRecord.setStatus(RESPONDENTEXPORTRECORD_STATUS_FAILED.getNumber());
        }*/

        CompletableFuture.runAsync(() -> asyncExportRspdData(project, param, dtype, releaseVer, userId, record_id, filemap, score_apply_type), executor)
                .exceptionally(ex -> {
                    deleteExportTaskLock(pid, dtype, SecurityContextHolder.getUserId());
                    log.error("创建导出答卷任务失败", ex);
                    exportRecord.setStatus(RESPONDENTEXPORTRECORD_STATUS_FAILED.getNumber());
                    throw new RuntimeException("创建导出答卷任务失败" + ex.getLocalizedMessage());
                }).whenComplete((unused, throwable) -> exportRecord.setTaskId(UUID.randomUUID().toString()));

        baseMapper.updateById(exportRecord);
    }

    /**
     * 删除锁置锁
     *
     * @param pid
     * @param dtype
     * @param userId
     */
    private void deleteExportTaskLock(String pid, String dtype, String userId) {
        redisService.del(getnLockKey(pid, dtype, userId));
    }

    /**
     * 生成lock_key, 每个用户的每种类型添加锁
     *
     * @param pid
     * @param dtype
     * @param userId
     */
    private String getnLockKey(String pid, String dtype, String userId) {
        return String.format("survey:rspd_export.%s.%s.%s", pid, dtype, userId);
    }

    /**
     * 导出答卷数据
     *
     * @param project
     * @param param
     * @param dtype
     * @param releaseVer
     * @param userId
     * @param record_id
     * @param filemap
     * @param score_apply_type
     */
    private void asyncExportRspdData(ProjectDto project, JSONObject param, String dtype, int releaseVer, String userId, String record_id, Map filemap, Boolean score_apply_type) {
        String pid = project.getId();
        //获取总条数
        /*LambdaQueryWrapper<SurveyRespondent> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SurveyRespondent::getProjectId, pid);
        queryWrapper.in(SurveyRespondent::getSource, RESPONDENT_FORMAL_SOURCE);
        queryWrapper.eq(SurveyRespondent::getDataStatus, RESPONDENT_DATA_STATUS_FORMAL_VALUE);
        queryWrapper.orderByDesc(SurveyRespondent::getBeginTime);*/

        List<SurveyRespondent> list = genFilterParams(param, project);
        //Long total_count = surveyRespondentMapper.selectCount(queryWrapper);
        Long total_count = (long) list.size();
        //获取敏感词map
        Map sensitive_map = surveyColumnService.getUidsSensitives(pid, userId);
        //Pair<String, File> result = genRspdData(project, param, dtype, releaseVer, sensitive_map, userId, record_id, total_count, score_apply_type, filemap);
        Pair<String, File> result = genRspdData(project, list, dtype, releaseVer, sensitive_map, userId, record_id, total_count, score_apply_type, filemap);
        String file_url = result.getValue0();
        File file = result.getValue1();
        log.error("export respondent data {}", file_url);
        SurveyRespondentExportRecord exportRecord = new SurveyRespondentExportRecord();
        exportRecord.setId(record_id);
        exportRecord.setStatus(RESPONDENTEXPORTRECORD_STATUS_SUCESS.getNumber());
        exportRecord.setFileSize(String.format("%.2f", file.length() / 1024.0) + "k");
        exportRecord.setFilePath(file_url);
        baseMapper.updateById(exportRecord);
        //文件保存成功后在把进度设置成100%
        setProgressBarStatus(SURVEY_RESPONDENT_EXPORT, record_id, total_count, total_count, 3700);
        file.delete();
    }

    private List<SurveyRespondent> genFilterParams(JSONObject param, ProjectDto project) {
        param = getFilter(param, project);
        return itemData2(project.getId(), param);
    }

    private List<SurveyRespondent> itemData2(String project_id, JSONObject params) {
        LambdaQueryWrapper<SurveyRespondent> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(SurveyRespondent::getSource, RESPONDENT_FORMAL_SOURCE);
        queryWrapper.eq(SurveyRespondent::getDataStatus, RESPONDENT_DATA_STATUS_FORMAL_VALUE);
        queryWrapper.eq(SurveyRespondent::getProjectId, project_id);
        //queryWrapper.in(SurveyRespondent::getStatus, Arrays.asList(params.getString("status").split(",")));
        queryWrapper.orderByDesc(SurveyRespondent::getBeginTime);
        Object qval;
        if (params.containsKey("data_status")) {
            qval = params.get("data_status");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<Integer>) qval), SurveyRespondent::getDataStatus, (List<Integer>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getDataStatus, qval);
            }
        }
        if (params.containsKey("status")) {
            qval = params.get("status");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<Integer>) qval), SurveyRespondent::getStatus, (List<Integer>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getStatus, qval);
            }
        }
        if (params.containsKey("pre_discard_status")) {
            qval = params.get("pre_discard_status");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<Integer>) qval), SurveyRespondent::getPreDiscardStatus, (List<Integer>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getPreDiscardStatus, qval);
            }
        }
        if (params.containsKey("seq")) {
            qval = params.get("seq");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<Long>) qval), SurveyRespondent::getSeq, (List<Long>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getSeq, qval);
            }
        }
        if (params.containsKey("version")) {
            qval = params.get("version");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<Integer>) qval), SurveyRespondent::getVersion, (List<Integer>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getVersion, qval);
            }
        }
        if (params.containsKey("source")) {
            qval = params.get("source");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<Integer>) qval), SurveyRespondent::getSource, (List<Integer>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getSource, qval);
            }
        }
        if (params.containsKey("client_type")) {
            qval = params.get("client_type");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<Integer>) qval), SurveyRespondent::getClientType, (List<Integer>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getClientType, Integer.valueOf((String) qval));
            }
        }
        if (params.containsKey("rspd_data_id")) {
            qval = params.get("rspd_data_id");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<String>) qval), SurveyRespondent::getRspdDataId, (List<String>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getRspdDataId, qval);
            }
        }
        if (params.containsKey("rspd_weixin_id")) {
            qval = params.get("rspd_weixin_id");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<String>) qval), SurveyRespondent::getRspdWeixinId, (List<String>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getRspdWeixinId, qval);
            }
        }
        if (params.containsKey("rspd_location_id")) {
            qval = params.get("rspd_location_id");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<String>) qval), SurveyRespondent::getRspdLocationId, (List<String>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getRspdLocationId, qval);
            }
        }
        if (params.containsKey("rspd_extra_id")) {
            qval = params.get("rspd_extra_id");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<String>) qval), SurveyRespondent::getRspdExtraId, (List<String>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getRspdExtraId, qval);
            }
        }
        if (params.containsKey("rspd_attachment_id")) {
            qval = params.get("rspd_attachment_id");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<String>) qval), SurveyRespondent::getRspdAttachmentId, (List<String>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getRspdAttachmentId, qval);
            }
        }
        if (params.containsKey("submit_seq")) {
            qval = params.get("submit_seq");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<Long>) qval), SurveyRespondent::getSubmitSeq, (List<Long>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getSubmitSeq, qval);
            }
        }
        if (params.containsKey("score")) {
            qval = params.get("score");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<Integer>) qval), SurveyRespondent::getScore, (List<Integer>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getClientType, qval);
            }
        }
        if (params.containsKey("deliver_id")) {
            qval = params.get("deliver_id");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<String>) qval), SurveyRespondent::getDeliverId, (List<String>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getDeliverId, qval);
            }
        }
        return surveyRespondentMapper.selectList(queryWrapper);
    }


    /**
     * 生成答卷df
     *
     * @param project
     * @param dtype
     * @param releaseVer
     * @param sensitive_map
     * @param userId
     * @param record_id
     * @param total_count
     * @param score_apply_type
     * @param filemap
     */
    private Pair<String, File> genRspdData(ProjectDto project, /*JSONObject param,*/ List<SurveyRespondent> cursors, String dtype, int releaseVer, Map sensitive_map, String userId, String record_id, Long total_count, Boolean score_apply_type, Map filemap) {
        String orgId = SecurityContextHolder.getOrgId();
        if ("spss_data".equals(dtype)) {
            total_count = total_count * 2;
        }
        int per = calcUpdateProgressStep(total_count);
        //设置进度条初始值
        setProgressBarStatus(0, record_id, 0, total_count, 3700);
        List<QstructsDto> qStructs = qdesGrpcClient.qdesListQstructWithq(project.getId(), releaseVer).stream().map(grpcConvertMapper::toQstructsDto).collect(Collectors.toList());
        Map<String, Object> retMap = getReportHeaderNew(project, qStructs.get(0), dtype, true, true, project.getCompanyId());
        JSONObject score_dict = surveyColumnService.getScoreGidDict(JSONObject.parseObject(qStructs.get(0).getScores()), qStructs.get(0));


        List<String> struct_columns = (List<String>) retMap.get("readable_headers");
        Map<String, JSONObject> qdict = (Map<String, JSONObject>) retMap.get("qdict");
        List<String> readable_headers = (List<String>) retMap.get("readable_headers");
        List readable_headers_v2 = (List<String>) retMap.get("readable_headers_v2");
        List<JSONObject> question_list = (List<JSONObject>) retMap.get("question_list");
        List<String> fixed_header = (List<String>) retMap.get("fixed_header");
        Map<Integer, List<String>> ctype_columns = (Map<Integer, List<String>>) retMap.get("ctype_columns");
        Map<String, String> auto_table_title_dict = (Map<String, String>) retMap.get("auto_table_title_dict");

        LambdaQueryWrapper<SurveyColumn> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SurveyColumn::getProjectId, project.getId()).eq(SurveyColumn::getCtype, COLUMN_CTYPE_META_FRONT.getNumber()).eq(SurveyColumn::getStatus, COLUMN_STATUS_DELETE.getNumber()).gt(SurveyColumn::getSortNo, 9).orderByAsc(SurveyColumn::getCtype).orderByAsc(SurveyColumn::getSortNo);
        List<SurveyColumn> levels_front = surveyColumnMapper.selectList(queryWrapper);
        //前半部分元数据字段
        List<String> columns_front = ctype_columns.getOrDefault(COLUMN_CTYPE_META_FRONT.getNumber(), Collections.emptyList());
        //后半部分元数据字段
        List<String> columns_behind = ctype_columns.getOrDefault(COLUMN_CTYPE_META_BEHIND.getNumber(), Collections.emptyList());
        //联系人字段
        List<String> columns_member = ctype_columns.getOrDefault(COLUMN_CTYPE_MEMBER.getNumber(), Collections.emptyList());
        //外部参数字段
        List<String> columns_ext_params = ctype_columns.getOrDefault(COLUMN_CTYPE_EXTERNAL.getNumber(), Collections.emptyList());
        //总分字段
        List<String> columns_score = ctype_columns.getOrDefault(COLUMN_CTYPE_SCORE.getNumber(), Collections.emptyList());
        List<List<Object>> datas = new ArrayList<>();

        //查询答卷数据
        /*LambdaQueryWrapper<SurveyRespondent> wrapper = new LambdaQueryWrapper<>();
        //TODO 条件查询用不了
        wrapper.eq(SurveyRespondent::getProjectId, project.getId())
                .in(SurveyRespondent::getSource, RESPONDENT_FORMAL_SOURCE)
                .eq(SurveyRespondent::getDataStatus, RESPONDENT_DATA_STATUS_FORMAL_VALUE)
                //筛选状态
                .in(StrUtil.isNotEmpty(param.getString("status")), SurveyRespondent::getStatus, Arrays.asList(param.getString("status").split(",")))
                //
                //.in(StrUtil.isNotEmpty(param.getString("deliver_id")), SurveyRespondent::getDeliverId, Arrays.asList(param.getString("deliver_id").split(",")))
                .orderByDesc(SurveyRespondent::getBeginTime);
        List<SurveyRespondent> cursors = surveyRespondentMapper.selectList(wrapper);*/
        int row = 0;
        //自增表格数据，数据格式如下：
        /*{
            "sheet1": [
            {"1": {"seq":"2003","column1":"value1","column2":"value2"}},
            {"2": {"seq":"2004","column1":"value1","column2":"value2"}}]
        }*/
        Map<String, List<Map<Integer, Map<String, Object>>>> auto_table_data = new HashMap();
        //自增表格列名
        Map<String, List> auto_table_colums = new HashMap();
        //自增表格列名
        Map<String, List> auto_table_colums_v2 = new HashMap();
        //自增表格列名{"sheet_name":{不可读列名：可读列名}}
        Map<String, Map<String, String>> auto_table_01_2_readable_column = new HashMap();
        Set<String> time_head = new HashSet();
        Set<String> date_head = new HashSet();
        date_head.add("生日");
        // 为了spss的数据类型，做统计 固定字段中，生日和答卷编号是整形
        Set<String> numerical_head = new HashSet();
        numerical_head.add("答卷编号");
        numerical_head.add("问卷版本");
        numerical_head.add("总分");
        numerical_head.add("答题时长（秒）");
        Set<String> date_time_head = new HashSet();
        numerical_head.add("答题开始时间");
        numerical_head.add("答题结束时间");
        Set string_head = new HashSet();
        string_head.addAll(fixed_header);
        string_head.removeAll(numerical_head);
        string_head.removeAll(date_time_head);

        Set<String> string_qtype = new HashSet();
        string_qtype.add("blank");
        string_qtype.add("multiple_blank");
        string_qtype.add("matrix_blank");
        string_qtype.add("city");

        //获取最新的外部参数
        SurveyExtParameter extparameters = getExtparameterByProjectId(project.getId(), null);
        Map<String, Integer> extparameters_type_map = new HashMap();
        List<JSONObject> extparametersData;
        if (Objects.nonNull(extparameters) && CollUtil.isNotEmpty((extparametersData = JSON.parseArray(extparameters.getData(), JSONObject.class)))) {
            extparametersData.forEach(data -> {
                extparameters_type_map.put(data.getString("name"), extParamsTypeFormat(data.get("dtype")));
                //布尔类型数据, 不做处理，按照0、 1 显示
                //整型数据类型数据, 不做处理，按照0、 1 显示
                if (EXTPARAMTER_DTYPE_STR.equals(data.get("dtype"))) {
                    //字符串类型数据
                    string_head.add(data.getString("name"));
                }
            });
        }
        //外部参数改版获取外部参数
        //外部参数改版，如果新的外部参数和已经存在的外部参数同名类型不同，以新的外部参数类型为准
        JSONObject query = new JSONObject();
        query.put("survey_id", project.getId());
        List<DeliverExtParamsDto> deliver_ext_param_list = deliverGrpcClient.listDeliverExtparams(query);
        deliver_ext_param_list.forEach(v -> {
            String key = StrUtil.isNotEmpty(v.getExtparamDesc()) ? String.format("%s|%s", v.getExtparamDesc(), v.getExtparamName()) : v.getExtparamName();
            extparameters_type_map.put(v.getExtparamName(), v.getExtparamType());
            if (EXTPARAM_DTYPE_DATA1.contains(v.getExtparamType())) {
                //字符串类型数据 外部参数更新之后，新增枚举类型也是字符串类型数据
                string_head.add(key);
            } else if (EXTPARAM_DTYPE_DATA_TIMESTAMP.getValue().equals(v.getExtparamType())) {
                if (string_head.contains(v.getExtparamName())) {
                    string_head.remove(key);
                }
                date_time_head.add(key);
            }
        });

        JSONObject enum_ext_params = surveyCommonService.getEnumExtParams(orgId, null);
        //获取当前用户是否有查看联系方式权限
        Map<Integer, Boolean> mdisplay_range_perm = new HashMap<>();
        if (StrUtil.isNotEmpty(userId)) {
            SurveyConstant.MEMBER_MTYPE_DICT.keySet().forEach(v -> mdisplay_range_perm.put(v, memberGrpcClient.checkMemberDisplayRangeByMtype(project.getCompanyId(), userId, v)));
        }
        for (SurveyRespondent v : cursors) {
            LambdaQueryWrapper<SurveyRespondentData> surveyWrapper = new LambdaQueryWrapper<>();
            surveyWrapper.eq(SurveyRespondentData::getRid, v.getId());
            SurveyRespondentData rspd_data = surveyRespondentDataMapper.selectOne(surveyWrapper);
            // mysql迁移：获取关联表数据
            LambdaQueryWrapper<SurveyRespondentWeixin> weiXinWrapper = new LambdaQueryWrapper<>();
            weiXinWrapper.eq(SurveyRespondentWeixin::getRid, v.getId());
            //取的是从库 wx_info = await model_utils.get_one_respondentweixin('USE_SLAVE', {'rid': surveyRespondent.id}, _noload_reference=True)
            SurveyRespondentWeixin surveyRespondentWeixin = surveyRespondentWeixinMapper.selectOne(weiXinWrapper);
            v.setWxInfo(surveyRespondentWeixin);

            if (Objects.isNull(rspd_data)) {
                rspd_data = new SurveyRespondentData();
                rspd_data.setAnswers(JSONObject.toJSONString(new HashMap()));
                rspd_data.setCustomAnswers(JSONObject.toJSONString(new HashMap()));
            }
            JSONObject answers = JSONObject.parseObject(rspd_data.getAnswers());
            List<Object> sub_datas = new ArrayList();
            SurveyRespondentData finalRspd_data = rspd_data;
            COLUMNS_META_SORT.forEach(s -> {
                if (s == COLUMN_CTYPE_META_FRONT.getNumber()) {
                    List<Map<String, Object>> sub_items = genMetaFront(new ArrayList<>(), v, levels_front, columns_front);
                    //删除第一条数据
                    sub_items.remove(0);
                    sub_items.forEach(j -> sub_datas.add(j.getOrDefault("answer", "")));
                } else if (s == COLUMN_CTYPE_META_BEHIND.getNumber()) {
                    List<Map<String, Object>> sub_items = genMetaBehind(new ArrayList<>(), v, project, columns_behind);
                    sub_items.forEach(j -> sub_datas.add(j.getOrDefault("answer", "")));
                } else if (s == COLUMN_CTYPE_SCORE.getNumber()) {
                    //columns_score：总分字段只有一个字段
                    List<Map<String, Object>> sub_items = genScoreByCalc(new ArrayList<>(), finalRspd_data, qStructs, columns_score);
                    sub_items.forEach(j -> sub_datas.add(j.getOrDefault("answer", "")));
                } else if (s == COLUMN_CTYPE_MEMBER.getNumber()) {
                    List<Map<String, Object>> sub_items = genMember(new ArrayList<>(), v, columns_member, mdisplay_range_perm);
                    sub_items.forEach(j -> sub_datas.add(HtmlUtil.cleanHtmlTag((String) j.getOrDefault("answer", ""))));
                } else if (s == COLUMN_CTYPE_EXTERNAL.getNumber()) {
                    List<Map<String, Object>> sub_items = genExtExparams(new ArrayList<>(), finalRspd_data, columns_ext_params, extparameters_type_map, enum_ext_params);
                    sub_items.forEach(j -> sub_datas.add(j.getOrDefault("answer", "")));
                } else if (s == COLUMN_CTYPE_QUESTION.getNumber()) {
                    Map answer_dict = new HashMap();
                    answers.forEach((gid, answer) -> {
                        JSONObject q = qdict.get(gid);
                        if (CollUtil.isEmpty(q)) {
                            return;
                        }
                        String qtype = q.getString("qtype");
                        //取 时间和日期题的 cid
                        if (QUESTION_TYPE_TIMEDELTA.getDispType().equals(qtype)) {
                            time_head.add((String) q.get("cid"));
                        } else if (QUESTION_TYPE_TIMESTAMP.getDispType().equals(qtype)) {
                            date_head.add((String) q.get("cid"));
                        }
                        String methodName = getAllDictMethod(dtype);
                        Map<Object, Object> all_answer;
                        Method method = null;
                        try {
                            method = this.getClass().getDeclaredMethod(methodName, JSONObject.class, Object.class, QstructsDto.class, JSONObject.class, String.class, Boolean.class);
                            method.setAccessible(true);
                            //通过反射调用下面的方法：getAllAnswerDictV2，getAllReadableDictV2
                            all_answer = (Map<Object, Object>) method.invoke(this, q, answer, qStructs.get(0), score_dict, dtype, score_apply_type);
                        } catch (Exception e) {
                            log.error("函数{}调用异常", methodName, e);
                            throw new BusinessException("函数调用异常：" + e.getMessage());
                        }
                        //遍历字段和数据
                        all_answer.forEach((head, val) -> {
                            //np.nan 可以理解为null
                            if (Objects.isNull(val)) {
                                return;
                            }
                            if (val instanceof String) {
                                //答案内容为str的列 添加到 string_head 中
                                string_head.add(head);
                                if (StrUtil.isNumeric((CharSequence) val) && ((String) val).length() < 10) {
                                    try {
                                        val = Integer.valueOf((String) val);
                                    } catch (Exception e) {
                                        log.error("整数转换异常：", e);
                                    }
                                }
                            } else if (val instanceof Float) {
                                if (!((String) head).endsWith("__score")) {
                                    val = (int) (float) val;
                                }
                            }
                            if (((String) head).endsWith("__score")) {
                                // 当该题选项没有评分的时候，即值为0或''时， 默认评分为0
                                if (ObjectUtil.isEmpty(val)) {
                                    val = 0;
                                }
                            }
                            answer_dict.put(head, val);
                        });
                        //处理自增表格题
                        if (QUESTION_TYPE_AUTO_TABLE.getValue().equals(qtype)) {
                            String sheet_name = HtmlUtil.cleanHtmlTag(String.format("%s_%s", q.getString("cid"), q.getString("title")));
                            sheet_name = sheetNameFormat(sheet_name);
                            List<Map<Integer, Map<String, Object>>> auto_t_data = auto_table_data.getOrDefault(sheet_name, new ArrayList());
                            List<String> auto_t_column = auto_table_colums.getOrDefault(sheet_name, new ArrayList(Arrays.asList("答卷编号")));
                            List<String> auto_t_column_v2 = auto_table_colums_v2.getOrDefault(sheet_name, new ArrayList(Arrays.asList("答卷编号")));
                            List<Map<String, Object>> extra_dict = handlerMap.get(QUESTION_TYPE_AUTO_TABLE.getValue()).getExtraAnswerDictListV2(q, (List<Map<String, List<String>>>) answer, false);

                            auto_table_01_2_readable_column.computeIfAbsent(sheet_name, k -> handlerMap.get(qtype).getSelectColumnChangeDictV2(q, qtype));

                            //自增表格题数据准备
                            Map<Integer, Map<String, Object>> at_map = new HashMap();
                            if (CollUtil.isNotEmpty(extra_dict)) {
                                for (int lineno = 0; lineno < extra_dict.size(); lineno++) {
                                    int finalLineno = lineno;
                                    at_map.computeIfAbsent(finalLineno, key -> {
                                        Map<String, Object> seq = new HashMap(2);
                                        seq.put("seq", v.getSeq());
                                        return seq;
                                    });

                                    Map<String, Object> extra_dict_data = extra_dict.get(lineno);
                                    extra_dict_data.forEach((col_name, val) -> {
                                        at_map.get(finalLineno).put(col_name, val);
                                        if (!auto_t_column.contains(col_name)) {
                                            auto_t_column.add(col_name);
                                            auto_t_column_v2.add(auto_table_title_dict.get(col_name));
                                        }
                                    });
                                }
                                auto_t_data.add(at_map);
                            }
                            auto_table_data.put(sheet_name, auto_t_data);
                            auto_table_colums.put(sheet_name, auto_t_column);
                            auto_table_colums_v2.put(sheet_name, auto_t_column_v2);
                        }
                    });
                    ((List<String>) retMap.get("q_gid_headers")).forEach(gid_header -> {
                        Object val = answer_dict.get(gid_header);
                        sub_datas.add(Optional.ofNullable(val).orElse(""));
                    });
                }
            });
            datas.add(sub_datas);
            //每per条更新一次进度
            if (row % per == 0) {
                setProgressBarStatus(SURVEY_RESPONDENT_EXPORT, record_id, row, total_count, 3700);
            }
            row += 1;
        }

        //创建数据sheet
        if ("readable".equals(dtype)) {
            //添加第一行数据
            datas.add(0, readable_headers_v2);
        }

        Map<Object, Object> df_detail = new HashMap();
        if (datas.size() > 0 && "anothernot01".equals(dtype)) {
            //存储表格数据
            df_detail.put("data", datas);
            //存储表格表头字段
            df_detail.put("columns", struct_columns);
            genAnotherNot01(datas, struct_columns, question_list, dtype);
        }

        //自增表格题
        auto_table_data.forEach((k, v) -> {
            List df_detail_autotable = null;
            List<List<Object>> auto_table_datas = new ArrayList();
            v.forEach(at -> {
                at.forEach((lineno, value) -> {
                    List<Object> sub_autotable_datas = new ArrayList();
                    //答题编号
                    sub_autotable_datas.add(value.get("seq"));
                    int start_pos = sub_autotable_datas.size();
                    List auto_table_colum = auto_table_colums.get(k);
                    auto_table_colum.subList(start_pos, auto_table_colum.size()).forEach(qkey -> sub_autotable_datas.add(value.getOrDefault(qkey, "")));
                    auto_table_datas.add(sub_autotable_datas);
                });
            });
            if (!auto_table_datas.isEmpty()) {
                if ("readable".equals(dtype)) {
                    auto_table_datas.add(0, auto_table_colums_v2.get(k));
                }
                if (auto_table_datas.size() > 0) {
                    //存储表格数据
                    df_detail_autotable = new ArrayList();
                    df_detail_autotable.add(auto_table_datas);
                    //存储表格表头
                    df_detail_autotable.add(auto_table_colums.get(k));
                }
            }
            if (Objects.nonNull(df_detail_autotable)) {
                auto_table_data.put(k, df_detail_autotable);
            }
        });

        Map<String, List<String>> senditive_columns_map = checkSensitive((JSONObject) sensitive_map, CollUtil.isNotEmpty(sensitive_map) ? (JSONObject) retMap.get("sensitive_dict") : new JSONObject());
        List senditive_columns = senditive_columns_map.get("senditive_columns");
        List<String> senditive_autotable_columns = senditive_columns_map.get("senditive_autotable_columns");

        if (!senditive_columns.isEmpty()) {
            df_detail.put(senditive_columns, "****");
        }
        if ("readable".equals(dtype) || "readable_merge_multiple".equals(dtype)) {
            //替换表格表头
            df_detail.put("columns", readable_headers);
        }
        if (!senditive_autotable_columns.isEmpty()) {
            //k指sheet页名称，v指sheet页内容
            auto_table_data.forEach((k, v) -> {
                //替换值当前sheet页中所有senditive_autotable_columns中的属性值为****
                //第一步：遍历sheet页的内容
                v.forEach(s -> {
                    //第二步：遍历每一行的数据
                    s.forEach((a, b) -> {
                        //第三步：遍历每一行的字段
                        b.forEach((i, j) -> {
                            if (senditive_autotable_columns.contains(i)) {
                                //替换字段值
                                b.put(i, "****");
                            }
                        });
                    });
                });
                //判断是否显示可读数据
                if (("readable".equals(dtype) || "readable_merge_multiple".equals(dtype)) && auto_table_01_2_readable_column.containsKey(k)) {
                    //获取需替换的字段，key为原来的字段，value为新的字段
                    Map<String, String> column = auto_table_01_2_readable_column.get(k);
                    //显示可读数据时，需要重新名称（即替换当前sheet的表头）
                    //第一步：遍历sheet页的内容
                    v.forEach(s -> {
                        //第二步：遍历每一行的数据
                        s.forEach((a, b) -> {
                            //第三步：遍历每一行的字段
                            b.forEach((i, j) -> {
                                if (column.containsKey(i)) {
                                    //替换字段名
                                    b.put(column.get(i), b.remove(i));
                                }
                            });
                        });
                    });
                }
                auto_table_data.put(k, v);
            });
        }

        Pair<String, File> result = null;

        //写文件
        if ("spss_data".equals(dtype)) {
        } else {
            //答卷数据
            List<String> columns = (List<String>) df_detail.get("columns");
            int answerStart = columns.indexOf("答题开始时间");
            int answerEnd = columns.indexOf("答题结束时间");
            datas.forEach(v -> {
                v.set(answerStart, ((String) v.get(answerStart)).replace("/", "-"));
                v.set(answerEnd, ((String) v.get(answerEnd)).replace("/", "-"));
            });
            // sheet页名称-自定义，如果没有则传空，先存储数据
            List<String> sheetNameList = new ArrayList<>();
            sheetNameList.add("data");
            List<List<List<String>>> headList = new ArrayList<>();
            List<List<String>> dataHeadList = new ArrayList<>();
            getTitleHead(dataHeadList, columns);
            headList.add(dataHeadList);
            List<List<List<Object>>> exportData = new ArrayList<>();
            exportData.add(datas);
            //码表
            if (D_TYPE_2.contains(dtype)) {
                Map<String, Object> res = genCodetable(project);
                List<List<Object>> codeDatas = (List<List<Object>>) res.get("datas");
                if (codeDatas.size() > 0) {
                    sheetNameList.add("码表");
                    headList.add(Collections.emptyList());
                    exportData.add(codeDatas);
                }
            }
            //自增表格题
            auto_table_data.forEach((k, v) -> {
                if (Objects.nonNull(v)) {
                    sheetNameList.add(k);
                    headList.add((List<List<String>>) ((List) v.get(1)).stream().map(Arrays::asList).collect(Collectors.toList()));
                    exportData.add((List<List<Object>>) v.get(0));
                }
            });
            String ossPath = OssPathEnum.TEMP_SURVEY.getPath() + SURVEY_EXPORT + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN) + SEPARATOR;
            //此处excel格式先忽略，后面补充
            //file_url = ExcelExportUtil.exportWithDynamicData2((String) filemap.get("fileName"), headList, exportData, sheetNameList, response, ossService, ossPath, OssPathEnum.TEMP_DELIVER);
            //file_url = ExcelExportUtil.exportWithDynamicData2((String) filemap.get("fileName"), headList, exportData, sheetNameList, response, storageService, ossPath, OssPathEnum.TEMP_DELIVER);
            //result = surveyCommonService.exportWithDynamicData((String) filemap.get("fileName"), headList, exportData, sheetNameList, null, ossPath);
            result = surveyCommonService.exportWithDynamicData((String) filemap.get("fileName"), headList, exportData, sheetNameList, null, ossPath);
        }
        //设置进度条为99%，1%留给保存文件，设置进度条结束值
        setProgressBarStatus(SURVEY_RESPONDENT_EXPORT, record_id, (long) (total_count * 0.99), total_count, 3700);
        return result;
    }

    private void getTitleHead(List<List<String>> headRowList, List<String> titelList) {
        for (String title : titelList) {
            List<String> rowHead = new ArrayList<>();
            rowHead.add(title);
            headRowList.add(rowHead);
        }
    }

    /**
     * 生成每个版本的码表
     *
     * @param projectDto
     */
    private Map<String, Object> genCodetable(ProjectDto projectDto) {
        List<List<Object>> datas = new ArrayList();
        List<QStruct> qstruct_list = qdesGrpcClient.qdesListQstructWithq(projectDto.getId(), projectDto.getVersion());
        AtomicInteger q_seq = new AtomicInteger(1);
        qstruct_list.forEach(qstruct -> {
            List<JSONObject> questionsList = surveyCommonService.walkQstructQuestions(JSONObject.parseObject(qstruct.getData()));
            questionsList.forEach(q -> {
                String qtype = q.getString("qtype");
                if (D_TYPE_3.contains(qtype)) {
                    datas.add(Arrays.asList(q_seq.get(), String.format("%s.%s", q.getString("cid"), BaseTypeHandler.stripHtmlWarp(q.getString("title")))));
                    datas.add(Arrays.asList("", "本题选项"));
                    List<JSONObject> options = (List<JSONObject>) q.getOrDefault("items", Collections.emptyList());
                    options.forEach(option -> datas.add(Arrays.asList("", String.format("%s.%s", option.getString("oid"), BaseTypeHandler.stripHtmlWarp(option.getString("title"))))));
                } else if (D_TYPE_4.contains(qtype)) {
                    datas.add(Arrays.asList(q_seq.get(), String.format("%s.%s", q.getString("cid"), BaseTypeHandler.stripHtmlWarp(q.getString("title")))));
                    datas.add(Arrays.asList("", "本题选项"));
                    List<JSONObject> options = (List<JSONObject>) q.getOrDefault("items", Collections.emptyList());
                    options.forEach(option -> datas.add(Arrays.asList("", String.format("%s.%s", option.getString("oid"), BaseTypeHandler.stripHtmlWarp(option.getString("title"))))));
                    datas.add(Arrays.asList("", "本题子题干"));
                    options = (List<JSONObject>) q.getOrDefault("rows_items", Collections.emptyList());
                    options.forEach(option -> datas.add(Arrays.asList("", String.format("%s.%s", option.getString("oid"), BaseTypeHandler.stripHtmlWarp(option.getString("title"))))));
                } else if ("cascader".equals(qtype)) {
                    //级联题,行业
                    List<JSONObject> option_list = (List<JSONObject>) q.get("option_list");
                    List<JSONObject> group_list = (List<JSONObject>) q.get("group_list");
                    option_list = option_list.stream().sorted((a, b) -> {
                        List<Integer> a1 = Arrays.stream(a.getString("ooid").split("_")).map(Integer::valueOf).collect(Collectors.toList());
                        List<Integer> b1 = Arrays.stream(b.getString("ooid").split("_")).map(Integer::valueOf).collect(Collectors.toList());
                        int minSize = Math.min(a1.size(), b1.size());
                        for (int i = 0; i < minSize; i++) {
                            if (!a1.get(i).equals(b1.get(i))) {
                                return a1.get(i).compareTo(b1.get(i));
                            }
                        }
                        return 0;
                    }).collect(Collectors.toList());
                    String[] otype = option_list.get(option_list.size() - 1).getString("otype").split("_");
                    Integer level_count = Integer.valueOf(otype[otype.length - 1]);
                    List<Integer> level_list = Convert.toList(Integer.class, NumberUtil.range(1, level_count));
                    List<JSONObject> finalOption_list = option_list;
                    level_list.forEach(level -> {
                        datas.add(Arrays.asList(q_seq.get(), String.format("%s__l%s.%s_第%s级_%s", q.get("cid"), level, BaseTypeHandler.stripHtmlWarp(q.getString("title")), level, group_list.get(level - 1))));
                        datas.add(Arrays.asList("", "本题选项"));
                        finalOption_list.forEach(option -> {
                            String[] type = option.getString("otype").split("_");
                            if (level.equals(Integer.valueOf(type[type.length - 1]))) {
                                datas.add(Arrays.asList("", String.format("%s.%s", option.get("oid"), BaseTypeHandler.stripHtmlWarp(q.getString("title")))));
                            }
                        });
                    });
                } else if ("multiple_blank".equals(qtype)) {
                    datas.add(Arrays.asList(q_seq.get(), String.format("%s.%s", q.getString("cid"), BaseTypeHandler.stripHtmlWarp(q.getString("title")))));
                    datas.add(Arrays.asList("", "本题选项"));
                    List<JSONObject> options = (List<JSONObject>) q.getOrDefault("items", Collections.emptyList());
                    options.forEach(option -> datas.add(Arrays.asList("", String.format("%s.%s", option.getString("oid"), BaseTypeHandler.stripHtmlWarp(option.getString("title"))))));
                } else if (D_TYPE_5.contains(qtype)) {
                    datas.add(Arrays.asList(q_seq.get(), String.format("%s.%s", q.getString("cid"), BaseTypeHandler.stripHtmlWarp(q.getString("title")))));
                    if ("proportion".equals(qtype)) {
                        datas.add(Arrays.asList("", String.format("本题比重总和: %s", q.getString("total"))));
                    } else {
                        List<Integer> socre = (List<Integer>) q.get("score_range");
                        datas.add(Arrays.asList("", String.format("本题分值(%s-%s)", socre.get(0), socre.get(socre.size() - 1))));
                    }
                    datas.add(Arrays.asList("", "本题选项"));
                    List<JSONObject> options = (List<JSONObject>) q.getOrDefault("items", Collections.emptyList());
                    if (options.size() == 1) {
                        datas.add(Arrays.asList("", BaseTypeHandler.stripHtmlWarp(String.format("%s.分值", options.get(0).getString("oid")))));
                    } else {
                        options.forEach(option -> datas.add(Arrays.asList("", String.format("%s.%s", option.getString("oid"), BaseTypeHandler.stripHtmlWarp(option.getString("title"))))));
                    }
                } else if ("nps".equals(qtype)) {
                    datas.add(Arrays.asList(q_seq.get(), String.format("%s.%s", q.getString("cid"), BaseTypeHandler.stripHtmlWarp(q.getString("title")))));
                    List<Integer> socre = (List<Integer>) q.get("score_range");
                    datas.add(Arrays.asList("", String.format("本题分值(%s-%s)", socre.get(0), socre.get(socre.size() - 1))));
                    datas.add(Arrays.asList("", "本题选项"));
                    datas.add(Arrays.asList("", "1.分值"));
                    datas.add(Arrays.asList("", "2.NPS客户类型(0-6分:贬低者   7-8分:中立者   9-10分:推荐者)"));
                } else if ("matrix_score".equals(qtype)) {
                    List<Integer> socre = (List<Integer>) q.get("score_range");
                    datas.add(Arrays.asList("", String.format("本题分值(%s-%s)", socre.get(0), socre.get(socre.size() - 1))));
                    datas.add(Arrays.asList("", "本题选项"));
                    List<JSONObject> options = (List<JSONObject>) q.getOrDefault("items", Collections.emptyList());
                    options.forEach(option -> datas.add(Arrays.asList("", String.format("%s.%s", option.getString("oid"), BaseTypeHandler.stripHtmlWarp(option.getString("title"))))));
                    datas.add(Arrays.asList("", "本题子题干"));
                    options = (List<JSONObject>) q.getOrDefault("rows_items", Collections.emptyList());
                    options.forEach(option -> datas.add(Arrays.asList("", String.format("%s.%s", option.getString("oid"), BaseTypeHandler.stripHtmlWarp(option.getString("title"))))));
                } else if ("geo".equals(qtype)) {
                    datas.add(Arrays.asList(q_seq.get(), String.format("%s.%s", q.getString("cid"), BaseTypeHandler.stripHtmlWarp(q.getString("title")))));
                    datas.add(Arrays.asList("", "本题选项"));
                    datas.add(Arrays.asList("", "地址.地址"));
                    datas.add(Arrays.asList("", "province.省份"));
                    datas.add(Arrays.asList("", "district.区县"));
                    datas.add(Arrays.asList("", "street.街道"));
                    datas.add(Arrays.asList("", "coordinate.经纬度"));
                } else if ("evaluation".equals(qtype)) {
                    datas.add(Arrays.asList(q_seq.get(), String.format("%s.%s", q.getString("cid"), BaseTypeHandler.stripHtmlWarp(q.getString("title")))));
                    datas.add(Arrays.asList("", "本题选项"));
                    datas.add(Arrays.asList("", "1.分值"));
                    datas.add(Arrays.asList("", "2.标签"));
                    if (ObjectUtil.isNotEmpty(q.get("write_evaluate"))) {
                        datas.add(Arrays.asList("", "3.开放"));
                    }
                } else if ("auto_table".equals(qtype)) {
                    datas.add(Arrays.asList(q_seq.get(), String.format("%s.%s", q.getString("cid"), BaseTypeHandler.stripHtmlWarp(q.getString("title")))));
                    datas.add(Arrays.asList("", "本题选项"));
                    List<JSONObject> options = (List<JSONObject>) q.getOrDefault("items", Collections.emptyList());
                    options.forEach(option -> datas.add(Arrays.asList("", String.format("%s.%s", option.getString("oid"), BaseTypeHandler.stripHtmlWarp(option.getString("title"))))));
                } else if ("city".equals(qtype)) {
                    datas.add(Arrays.asList(q_seq.get(), String.format("%s.%s", q.getString("cid"), BaseTypeHandler.stripHtmlWarp(q.getString("title")))));
                    datas.add(Arrays.asList("", "本题选项"));
                    datas.add(Arrays.asList("", "1.省份"));
                    Integer info_level = q.getInteger("info_level");
                    if (info_level == 2) {
                        datas.add(Arrays.asList("", "2.城市"));
                    } else if (info_level == 3) {
                        datas.add(Arrays.asList("", "2.城市"));
                        datas.add(Arrays.asList("", "3.区县"));
                    } else if (info_level == 4) {
                        datas.add(Arrays.asList("", "2.城市"));
                        datas.add(Arrays.asList("", "2.区县"));
                        datas.add(Arrays.asList("", "4.街道信息"));
                    }
                } else if ("blank".equals(qtype)) {
                    datas.add(Arrays.asList(q_seq.get(), String.format("%s.%s", q.getString("cid"), BaseTypeHandler.stripHtmlWarp(q.getString("title")))));
                    datas.add(Arrays.asList("", "本题选项"));
                    datas.add(Arrays.asList("", "1.填空"));
                } else if ("timedelta".equals(qtype)) {
                    datas.add(Arrays.asList(q_seq.get(), String.format("%s.%s", q.getString("cid"), BaseTypeHandler.stripHtmlWarp(q.getString("title")))));
                    datas.add(Arrays.asList("", "本题选项"));
                    datas.add(Arrays.asList("", "1.时间"));
                } else if ("timestamp".equals(qtype)) {
                    datas.add(Arrays.asList(q_seq.get(), String.format("%s.%s", q.getString("cid"), BaseTypeHandler.stripHtmlWarp(q.getString("title")))));
                    datas.add(Arrays.asList("", "本题选项"));
                    datas.add(Arrays.asList("", "1.日期"));
                }
                q_seq.incrementAndGet();
            });
        });
        Map<String, Object> data = new HashMap(4);
        data.put("datas", datas);
        data.put("columns", Arrays.asList("序号", "格式详情"));
        return data;
    }


    public String replaceOtherCharacter(String value) {
        String regEx = "[`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~！@#￥%……&*()——+|{}【】‘；：”“’。，、？\\\\]+.*";
        Pattern p = Pattern.compile(regEx);
        Matcher m = p.matcher(value);
        return m.replaceAll("_");
    }

    private Map<String, List<String>> checkSensitive(JSONObject sensitive_map, JSONObject sensitive_dict) {
        List<String> senditive_columns = new ArrayList();
        List<String> senditive_autotable_columns = new ArrayList();
        Map<String, String> new_sensitive_dict = (Map<String, String>) sensitive_dict.get("01");
        if (CollUtil.isNotEmpty(new_sensitive_dict)) {
            new_sensitive_dict.forEach((gid, vals) -> {
                if (surveyColumnService.checkQuestionSensitive(sensitive_map, gid)) {
                    senditive_columns.add(vals);
                    if (((List) sensitive_dict.get("auto_table_gids")).contains(gid)) {
                        senditive_autotable_columns.add(vals);
                    }
                }
            });
        }
        Map<String, List<String>> res = new HashMap(4);
        res.put("senditive_columns", senditive_columns);
        res.put("senditive_autotable_columns", senditive_autotable_columns);
        return res;
    }

    private void genAnotherNot01(List<List<Object>> datas, List<String> struct_columns, List<JSONObject> question_list, String dtype) {
        //another_not01
        question_list.forEach(v -> {
            String qtype = v.getString("qtype");
            if ("multiple".equals(qtype) || "matrix_multiple".equals(qtype)) {
                List<String> col_list = handlerMap.get(dtype).getSelectColumnListV2(v.getJSONObject("data"), dtype);
                if ("matrix_multiple".equals(qtype)) {
                    //矩阵多选题，分类题
                    List<String> row_oid_list = ((List<JSONObject>) v.getOrDefault("rows_items", Collections.emptyList())).stream().map(i -> (String) i.getOrDefault("oid", "")).collect(Collectors.toList());
                    genMatrixAnotherNot01Data(datas, struct_columns, row_oid_list, col_list, ((List<JSONObject>) v.getOrDefault("items", Collections.emptyList())).size());
                } else {
                    datas.forEach(data -> {
                        List<Object> value_list = new ArrayList();
                        col_list.forEach(key -> {
                            Object value = data.get(struct_columns.indexOf(key));
                            if (!"".equals(value)) {
                                value_list.add(value);
                            }
                        });
                        for (int i = 0; i < col_list.size() - value_list.size(); i++) {
                            value_list.add("");
                        }
                        //重新插入数据
                        for (int i = 0; i < col_list.size(); i++) {
                            String key = col_list.get(i);
                            try {
                                data.set(struct_columns.indexOf(key), value_list.get(i));
                            } catch (Exception e) {
                                log.info("矩阵多选题，分类题数据异常:", e);
                                //将这一列的数据全部转换为String类型
                                datas.forEach(s -> s.set(struct_columns.indexOf(key), String.valueOf(s.get(struct_columns.indexOf(key)))));
                                data.set(struct_columns.indexOf(key), value_list.get(i));
                            }
                        }
                    });
                }
            }
        });
    }


    /**
     * 矩阵多选题，分类题， 非01左对齐 数据整理
     *
     * @param datas
     * @param struct_columns
     * @param row_oid_list   矩阵题 行数
     * @param col_list       矩阵题 每行的 列数
     * @param cols           矩阵题的 选项列
     */
    private void genMatrixAnotherNot01Data(List<List<Object>> datas, List<String> struct_columns, List<String> row_oid_list, List<String> col_list, int cols) {
        //答卷数据条数
        datas.forEach(data -> {
            List<String> value_list = new ArrayList();
            //每道题 行数
            row_oid_list.forEach(row_oid -> {
                List<String> row_value_list = new ArrayList();
                //遍历 选项列，根据 row 判断是否为同一行数据 Q1_1_1, Q1_1_2, Q1_2_1, Q1_2_2
                col_list.forEach(key -> {
                    String value = (String) data.get(struct_columns.indexOf(key));
                    if (row_oid.equals(key.split("__")[1]) && !"".equals(value)) {
                        row_value_list.add(value);
                    }
                });
                //根据列数 把选项左移，原位置填 ''
                for (int i = 0; i < cols - row_value_list.size(); i++) {
                    row_value_list.add("");
                }
            });
            value_list.addAll(row_oid_list);
            //按照col_list 把 左移的数据 按照索引位 重新插入数据
            for (int i = 0; i < col_list.size(); i++) {
                String key = col_list.get(i);
                try {
                    data.set(struct_columns.indexOf(key), value_list.get(i));
                } catch (Exception e) {
                    log.info("矩阵多选题，分类题数据异常：", e);
                    //将这一列的数据全部转换为String类型
                    datas.forEach(v -> v.set(struct_columns.indexOf(key), String.valueOf(v.get(struct_columns.indexOf(key)))));
                    data.set(struct_columns.indexOf(key), value_list.get(i));
                }
            }
        });
    }

    /**
     * 格式化sheet名称
     *
     * @param sheet_name sheet名称
     * @return
     */
    private String sheetNameFormat(String sheet_name) {
        //sheet_name 长度不能超过31个字符
        sheet_name = sheet_name.substring(0, Math.min(sheet_name.length(), 31));
        if (StrUtil.isNotEmpty(sheet_name)) {
            //替换文件名中的特殊字符为“_”
            String regEx = "[\\\\,/,:,*,?,\\[,\\]]";
            Pattern p = Pattern.compile(regEx);
            Matcher m = p.matcher(sheet_name);
            sheet_name = m.replaceAll("_").trim();
        }
        return sheet_name;
    }

    /**
     * 从子类获取get_select_answer_dict()方法和get_open_answer_dict()方法把获取到的值合并到一个dict中
     *
     * @param data
     * @param answers          数据库中存的原始答案  例：{"Q1": [1, 2, 3, 4, 5], "Q4": 4, "Q5": 5, "Q6": 4, "Q7": 5, "Q8": 4, "Q9": 5, "Q10": 4, "Q2": 4, "Q3": [4, 4, 4, 4, 4, 4, 4, 4], "Q16": 9, "Q19": "", "Q11": 1, "Q12": 4, "Q13": 5, "Q14": 4, "Q15": 4, "Q17": 1, "Q18": 3}
     * @param qstructsDto
     * @param score_dict
     * @param dtype
     * @param score_apply_type
     * @return 返回存入h5文件中数据格式 例：Q1题：{"Q1_1": 1, "Q1_2": 1, "Q1_3": 1, "Q1_4": 1, "Q1_5": 1, "Q1_6": 0, "Q1_6__other": NaN}
     */
    private Map<Object, Object> getAllAnswerDictV2(JSONObject data, Object answers, QstructsDto qstructsDto, JSONObject score_dict, String dtype, Boolean score_apply_type) {
        Map<Object, Object> answer_dict = new HashMap();
        BaseTypeHandler typeHandler = handlerMap.get(dtype);
        Map<Object, Object> select_answer = typeHandler.getSelectAnswerDictV2(data, answers, dtype, score_apply_type);
        answer_dict.putAll(select_answer);
        Map<Object, Object> open_answer = typeHandler.getOpenAnswerDictV2(data, qstructsDto);
        answer_dict.putAll(open_answer);
        Map<Object, Object> score_answer = typeHandler.getScoreAnswerDictV2(data, answers, qstructsDto, score_dict, dtype);
        answer_dict.putAll(score_answer);
        return answer_dict;
    }

    /**
     * 类似get_all_answer_dict, 但是答案是可读数据
     *
     * @param data
     * @param answers
     * @param qstructsDto
     * @param score_dict
     * @param dtype
     * @param score_apply_type
     */
    private Map<Object, Object> getAllReadableDictV2(JSONObject data, Object answers, QstructsDto qstructsDto, JSONObject score_dict, String dtype, Boolean score_apply_type) {
        Map<Object, Object> answer_dict = new HashMap();
        String qtype = data.getString("qtype");
        Map<Object, Object> select_readable = getSelectReadableDictV2(data, qtype, answers, dtype, score_apply_type);
        if (CollUtil.isNotEmpty(select_readable)) {
            answer_dict.putAll(select_readable);
        }
        BaseTypeHandler typeHandler = handlerMap.get(qtype);
        Map<Object, Object> open_readable = typeHandler.getOpenReadableDictV2(data, answers);
        if (CollUtil.isNotEmpty(open_readable)) {
            answer_dict.putAll(open_readable);
        }
        Map<Object, Object> score_readable = typeHandler.getScoreReadableDictV2(data, answers, qstructsDto, score_dict, dtype);
        if (CollUtil.isNotEmpty(score_readable)) {
            answer_dict.putAll(score_readable);
        }
        return answer_dict;
    }

    private Map<Object, Object> getSelectReadableDictV2(JSONObject data, String qtype, Object answers, String dtype, Boolean score_apply_type) {
        BaseTypeHandler typeHandler = handlerMap.get(qtype);
        Map<String, Map<Object, Object>> conv_tab = typeHandler.getConvertTableV2(data);
        Map select_readable = new HashMap();
        Map<Object, Object> select_answer = typeHandler.getSelectAnswerDictV2(data, answers, dtype, score_apply_type);
        select_answer.forEach((key, val) -> {
            Map<Object, Object> conv = conv_tab.get(key);
            if (CollUtil.isNotEmpty(conv)) {
                if ("multiple".equals(qtype) || "matrix_single".equals(qtype)) {
                    select_readable.put(key, conv.getOrDefault(val, val));
                } else {
                    select_readable.put(key, conv.get(val));
                }
            } else {
                //级联题，行业题，评价题分值
                select_readable.put(key, val);
            }
        });
        return select_readable;
    }

    public String getAllDictMethod(String dtype) {
        if ("readable".equals(dtype)) {
            return "getAllReadableDictV2";
        } else if (RESPONDENT_EXPORT_MAP2.containsKey(dtype)) {
            return "getAllAnswerDictV2";
        }
        return null;
    }

    @Override
    public List<Map<String, Object>> genExtExparams(List<Map<String, Object>> sub_items, SurveyRespondentData rspd_data, List<String> columns_ext_params, Map<String, Integer> extparameters_type_map, JSONObject enum_ext_params) {
        columns_ext_params.forEach(column -> {
            if (column.trim().contains("|")) {
                // 外部参数列改为 描述 | 名称
                String[] value = column.trim().split("\\|");
                column = value[value.length - 1];
            }
            Integer dtype = extparameters_type_map.getOrDefault(column, EXTPARAM_DTYPE_DATA_STRING.getValue());
            JSONObject custom_answers = JSONObject.parseObject(rspd_data.getCustomAnswers());
            Object columnData = custom_answers.get(column.trim());
            if (EXTPARAM_DTYPE_DATA_STRING.getValue().equals(dtype)) {
                sub_items.add(toRowsObj(Optional.ofNullable(columnData).orElse("")));
            } else if (EXTPARAM_DTYPE_DATA_BOOL.getValue().equals(dtype)) {
                //True 显示为1 False 显示为0
                if (Objects.isNull(columnData) || "".equals(columnData)) {
                    sub_items.add(toRowsObj(""));
                } else {
                    if (Arrays.asList("true", "1", "True", "TRUE").contains(columnData.toString())) {
                        sub_items.add(toRowsObj(1));
                    } else {
                        sub_items.add(toRowsObj(0));
                    }
                }
            } else if (EXTPARAMTER_DTYPE_INT.equals(dtype)) {
                //整型数据显示整型
                sub_items.add(Objects.nonNull(columnData) ? toRowsObj(columnData) : toRowsObj(""));
            } else if (EXTPARAM_DTYPE_DATA_FLOAT.getValue().equals(dtype)) {
                //浮点数据显示浮点数据
                sub_items.add(Objects.nonNull(columnData) ? toRowsObj(columnData) : toRowsObj(""));
            } else if (EXTPARAM_DTYPE_DATA_TIMESTAMP.getValue().equals(dtype)) {
                //timestamp类型数据显示timestamp数据
                try {
                    sub_items.add(Objects.nonNull(columnData) ? toRowsObj(new SimpleDateFormat("yyyyMMddHHmmss").format(columnData)) : toRowsObj(""));
                } catch (Exception e) {
                    sub_items.add(toRowsObj(""));
                    log.error("答卷数据列表数据，外部参数类型字段转换：", e);
                }
            } else if (EXTPARAM_DTYPE_DATA_ENUM.getValue().equals(dtype)) {
                //enum类型数据显示enum类型数据
                if (Objects.isNull(columnData)) {
                    sub_items.add(toRowsObj(""));
                } else {
                    JSONObject ext_params_code_map = enum_ext_params.getJSONObject(column);
                    if (CollUtil.isEmpty(ext_params_code_map)) {
                        //外部参数的枚举值被删除
                        log.info("外部参数枚举类型{},枚举值被删除", column);
                        sub_items.add(toRowsObj(""));
                    } else {
                        sub_items.add(toRowsObj(ext_params_code_map.getOrDefault(columnData, "")));
                    }
                }
            } else {
                sub_items.add(toRowsObj(Optional.ofNullable(columnData).orElse("")));
            }
        });
        return sub_items;
    }

    @Override
    public List<Map<String, Object>> genMember(List<Map<String, Object>> sub_items, SurveyRespondent surveyRespondent, List<String> columns_member, Map<Integer, Boolean> mdisplay_range_perm) {
        MemberDto memberDto = null;
        if (StrUtil.isNotEmpty(surveyRespondent.getMemberId())) {
            memberDto = memberGrpcClient.getMemberById(surveyRespondent.getMemberId());
            //隐藏联系人联系方式
            if (Objects.nonNull(memberDto) && !mdisplay_range_perm.getOrDefault(memberDto.getMemberType(), true)) {
                memberDto.setMobile(StrUtil.isNotEmpty(memberDto.getMobile()) ? surveyColumnService.hideMobile(memberDto.getMobile(), true) : memberDto.getMobile());
                memberDto.setEmail(StrUtil.isNotEmpty(memberDto.getEmail()) ? surveyColumnService.hideEmail(memberDto.getEmail(), true) : memberDto.getEmail());
            }
        }
        MemberDto finalMemberDto = memberDto;
        MemberDto finalMemberDto1 = memberDto;
        columns_member.forEach(v -> {
            BiFunction<SurveyRespondent, MemberDto, Map<String, Object>> function = META_MEMBER_FUNC.get(v);
            if (Objects.nonNull(finalMemberDto) && Objects.nonNull(function)) {
                if (COLUMNS_MEMBER.get(6).equals(v) || COLUMNS_MEMBER.get(7).equals(v)) {
                    sub_items.add(function.apply(surveyRespondent, finalMemberDto1));
                } else {
                    sub_items.add(function.apply(null, finalMemberDto1));
                }
            } else {
                sub_items.add(toRowsObj(""));
            }
        });
        return sub_items;
    }

    /**
     * 计算总分，通过rspd_data,和最新的评分规则
     *
     * @param sub_items
     * @param rspd_data
     * @param qstruct_list
     * @param columns_score
     * @return
     */
    @Override
    public List<Map<String, Object>> genScoreByCalc(List<Map<String, Object>> sub_items, SurveyRespondentData rspd_data, List<QstructsDto> qstruct_list, List<String> columns_score) {
        if (ObjectUtils.isEmpty(columns_score)) {
            return sub_items;
        }
        QstructsDto qstruct = qstruct_list.get(0);
        String scores = qstruct.getScores();
        if (StrUtil.isEmpty(scores) || StrUtil.equals("{}", scores)) {
            sub_items.add(toRowsObj(0));
            return sub_items;
        }
        Integer version = qstruct.getVersion();
        String answers = rspd_data.getAnswers();

        //版本号:评分规则 做缓存使用，py每次请求缓存会初始化，java为避免不同项目相同版本号冲突，此处加上项目ID
        JSONObject gid_score_dict = (JSONObject) xmSurveyCache.getIfPresent(qstruct.getProjectId() + ":" + version);
        if (Objects.isNull(gid_score_dict)) {
            gid_score_dict = surveyColumnService.getScoreGidDict(JSONObject.parseObject(scores), qstruct);
            xmSurveyCache.put(qstruct.getProjectId() + ":" + version, gid_score_dict);
        }
        //实际上这个gid_score_dict是不需要在外面计算的，因为gid到分数的对应关系只需要计算不需要io
        //不过之前既然使用了一个缓存，那大概其是有进行缓存的必要性的。所以此处进行了保留
        Object final_score = calcScore(answers, qstruct, gid_score_dict);
        if (!(final_score instanceof Float || final_score instanceof Integer)) {
            sub_items.add(toRowsObj(""));
        } else {
            sub_items.add(toRowsObj(final_score));
        }
        return sub_items;
    }

    /**
     * 计算答卷总分
     *
     * @param answers        答卷答案
     * @param qstruct        问卷结构 --> project.js
     * @param gid_score_dict gid和分数的对应关系[可选参数]
     */
    private Object calcScore(String answers, QstructsDto qstruct, JSONObject gid_score_dict) {
        JSONObject qtype_dict = new JSONObject();
        JSONObject scoreConfig = JSONObject.parseObject(Optional.ofNullable(qstruct.getScoreConfig()).orElse("{}"));
        String scores = qstruct.getScores();
        if (StrUtil.isEmpty(scores)) {
            return "";
        }
        //生成gid和分数的对应关系 一般来说是没有io的，只是进行了一系列计算
        if (ObjectUtils.isEmpty(gid_score_dict)) {
            gid_score_dict = surveyColumnService.getScoreGidDict(JSONObject.parseObject(scores), qstruct);
        }
        parseQuestionsItem(JSONObject.parseObject(qstruct.getData()).getJSONArray("items"), qtype_dict);
        AtomicReference<Float> final_score = new AtomicReference<>(0f);
        JSONObject finalGid_score_dict = gid_score_dict;
        JSONObject answersJson = JSONObject.parseObject(answers);
        answersJson.forEach((qgid, anstmp) -> {
            if (ObjectUtils.isEmpty(anstmp)) {
                return;
            }
            String qtype = qtype_dict.getJSONObject(qgid).getString("qtype");
            if ("single".equals(qtype)) {
                Float val = convertFloat(finalGid_score_dict.getOrDefault(String.format("%s_%s", qgid, ((JSONArray) anstmp).getString(0)), "").toString());
                if (Objects.nonNull(val)) {
                    final_score.updateAndGet(v1 -> v1 + val);
                }
            } else if ("multiple".equals(qtype)) {
                String custom_qtype = qtype_dict.getJSONObject(qgid).getString("custom_qtype");
                if (!"image_select".equals(custom_qtype) && SCORE_RULE_ALL_RIGHT.equals(scoreConfig.getInteger(qgid)) && BaseTypeHandler.checkAnswerAllRight(qgid, finalGid_score_dict, anstmp, qtype)) {
                    //如果设置了全对才得分，若答案内容不全对，则得分为0，直接跳过
                    return;
                }
                //多选题答案格式：[[1],[1,2],[2,4]]
                ((JSONArray) anstmp).forEach(subans -> {
                    if (ObjectUtils.isEmpty(subans)) {
                        return;
                    }
                    Float val = convertFloat(finalGid_score_dict.getOrDefault(String.format("%s_%s", qgid, ((JSONArray) subans).getString(0)), "").toString());
                    if (Objects.nonNull(val)) {
                        final_score.updateAndGet(v1 -> v1 + val);
                    }
                });
            } else if ("matrix_single".equals(qtype)) {
                ((JSONObject) anstmp).forEach((rgid, subans) -> {
                    if (ObjectUtils.isEmpty(subans)) {
                        return;
                    }
                    Float val = convertFloat(finalGid_score_dict.getOrDefault(String.format("%s_%s%s", qgid, rgid, ((JSONArray) subans).getString(0)), "").toString());
                    if (Objects.nonNull(val)) {
                        final_score.updateAndGet(v1 -> v1 + val);
                    }
                });
            } else if ("matrix_multiple".equals(qtype)) {
                //矩阵多选题答案格式：{"13":[11],"14":[12]}
                ((JSONObject) anstmp).forEach((rgid, subans) -> {
                    ((JSONArray) subans).forEach(olist -> {
                        if (ObjectUtils.isEmpty(olist)) {
                            return;
                        }
                        JSONObject row_gid_score_dict = new JSONObject();
                        finalGid_score_dict.forEach((i, j) -> {
                            if (i.startsWith(String.format("%s_%s", qgid, rgid))) {
                                row_gid_score_dict.put(i, j);
                            }
                        });
                        JSONObject row_answer_ids = new JSONObject();
                        row_answer_ids.put(rgid, subans);
                        if (!SCORE_RULE_ALL_RIGHT.equals(scoreConfig.getInteger(qgid)) || !BaseTypeHandler.checkAnswerAllRight(qgid, row_gid_score_dict, row_answer_ids, qtype)) {
                            Float val = convertFloat(finalGid_score_dict.getOrDefault(String.format("%s_%s_%s", qgid, rgid, ((JSONArray) olist).getString(0)), "").toString());
                            if (Objects.nonNull(val)) {
                                final_score.updateAndGet(v1 -> v1 + val);
                            }
                        }
                    });
                });
            }
        });
        return Float.parseFloat(String.format("%.2f", final_score.get()));
    }

    /**
     * 字符串转换成浮点型并保留2位小数
     *
     * @param val)
     */
    private Float convertFloat(String val) {
        try {
            return StrUtil.isNotEmpty(val) ? Float.parseFloat(String.format("%.2f", Float.parseFloat(val))) : null;
        } catch (Exception e) {
            log.error("字符串转换成浮点型并保留2位小数异常", e);
        }
        return null;
    }

    private void parseQuestionsItem(JSONArray items, JSONObject qtype_dict) {
        if (ObjectUtils.isEmpty(items)) {
            return;
        }
        int len = items.size();
        for (int i = 0; i < len; i++) {
            JSONObject item = items.getJSONObject(i);
            if (!item.containsKey("type")) {
                continue;
            }
            if ("G".equals(item.getString("type"))) {
                parseQuestionsItem(item.getJSONArray("items"), qtype_dict);
            } else if ("Q".equals(item.getString("type"))) {
                String gid = item.getString("gid");
                if (qtype_dict.containsKey(gid)) {
                    qtype_dict.getJSONObject(gid).put("qtype", item.getOrDefault("qtype", ""));
                    qtype_dict.getJSONObject(gid).put("custom_qtype", item.getOrDefault("custom_qtype", ""));
                } else {
                    JSONObject tmp = new JSONObject();
                    tmp.put("qtype", item.getOrDefault("qtype", ""));
                    tmp.put("custom_qtype", item.getOrDefault("custom_qtype", ""));
                    qtype_dict.put(gid, tmp);
                }
            }
        }
    }

    @Override
    public List<Map<String, Object>> genMetaBehind(List<Map<String, Object>> sub_items, SurveyRespondent surveyRespondent, ProjectDto project, List<String> columns_behind) {
        Set<String> project_params = new HashSet<>();
        project_params.add(COLUMNS_META_BEHIND.get(21));
        project_params.add(COLUMNS_META_BEHIND.get(22));
        columns_behind.forEach(v -> {
            Object params = project_params.contains(v) ? project : surveyRespondent;
            Function<Object, Map<String, Object>> function = META_BEHIND_FUNC.get(v);
            if (Objects.nonNull(function)) {
                sub_items.add(function.apply(params));
            } else {
                sub_items.add(toRowsObj(""));
            }
        });
        return sub_items;
    }

    @Override
    public List<Map<String, Object>> genMetaFront(List<Map<String, Object>> sub_items, SurveyRespondent surveyRespondent, List<SurveyColumn> levels_front, List<String> columns_front) {
        Map<String, Object> first_ans = toRowsObj(surveyRespondent.getSeq());
        first_ans.put("rid", surveyRespondent.getId());
        first_ans.put("status", surveyRespondent.getStatus());
        LambdaQueryWrapper<SurveyRespondentAttachment> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SurveyRespondentAttachment::getProjectId, surveyRespondent.getProjectId());
        queryWrapper.eq(SurveyRespondentAttachment::getSeq, surveyRespondent.getSeq());
        Long att_count = surveyRespondentAttachmentMapper.selectCount(queryWrapper);
        first_ans.put("attachment", att_count > 0);
        //seq列隐藏，就使用这个字段操作列
        sub_items.add(first_ans);
        List<String> group_region = levels_front.stream().map(SurveyColumn::getCol).distinct().collect(Collectors.toList());
        columns_front.forEach(v -> {
            if (!group_region.contains(v)) {
                Function<SurveyRespondent, Map<String, Object>> function = META_FRONT_FUNC.get(v);
                sub_items.add(Objects.nonNull(function) ? function.apply(surveyRespondent) : toRowsObj(""));
            }
        });
        List<String> groups = new ArrayList<>();
        String groupId = surveyRespondent.getGroupId();
        if (StrUtil.isNotEmpty(groupId) && (columns_front.contains(COLUMNS_META_FRONT.get(8)) || columns_front.contains(COLUMNS_META_FRONT.get(9)))) {
            GroupDto group = userGrpcClient.getGroup(groupId);
            String group_name = Optional.ofNullable(group).map(GroupDto::getTitle).orElse("");
            List<String> ancestors = Optional.ofNullable(group).map(GroupDto::getAncestors).orElse(Collections.emptyList());
            ancestors = new ArrayList<>(ancestors);
            //Collections.reverse(ancestors);
            ancestors.forEach(v -> {
                GroupDto parent_group = userGrpcClient.getGroup(v);
                groups.add(Optional.ofNullable(parent_group).map(GroupDto::getTitle).orElse(""));
            });
            groups.add(group_name);
        }
        for (int i = 0; i < levels_front.size(); i++) {
            if (columns_front.contains(levels_front.get(i).getCol())) {
                String gname = groups.size() > i ? groups.get(i) : "";
                //小区、大区、全国…，每个层级一列
                sub_items.add(toRowsObj(gname));
            }
        }
        return sub_items;
    }

    /**
     * 生成答卷数据列表rows，元素为dict类型
     *
     * @param value
     * @return
     */
    private Map<String, Object> toRowsObj(Object value) {
        Map<String, Object> data = new HashMap<>();
        if (Objects.isNull(value)) {
            //if ((value instanceof Integer) || (value instanceof Float)) {
            //    data.put("answer", 0);
            //}
            data.put("answer", "");
        } else {
            data.put("answer", value);
        }
        return data;
    }


    /**
     * 因为外部参数数据库中类型存的有string,int,boolean,还有1,2,3等
     * 现在新的外部参数用到答卷列表里面，老的数据类型要转成新的数据类型来使用
     *
     * @param dtype
     * @return
     */
    @Override
    public Integer extParamsTypeFormat(Object dtype) {
        if (dtype instanceof Integer) {
            int value = (int) dtype;
            if (value == EXTPARAMTER_DTYPE_STR) {
                return EXTPARAM_DTYPE_DATA_STRING.getValue();
            } else if (value == EXTPARAMTER_DTYPE_INT) {
                return EXTPARAM_DTYPE_DATA_INT.getValue();
            } else if (value == EXTPARAM_DTYPE_DATA_BOOL.getValue()) {
                return EXTPARAM_DTYPE_DATA_BOOL.getValue();
            }
        }
        if (dtype instanceof String) {
            String value = (String) dtype;
            switch (value) {
                case "string":
                    return EXTPARAM_DTYPE_DATA_STRING.getValue();
                case "int":
                    return EXTPARAM_DTYPE_DATA_INT.getValue();
                case "boolean":
                    return EXTPARAM_DTYPE_DATA_BOOL.getValue();
            }
        }
        return null;
    }

    private SurveyExtParameter getExtparameterByProjectId(String projectId, String version) {
        return surveyExtParameterService.getExtParam(projectId, version, Boolean.FALSE);
    }

    /**
     * @param project
     * @param qStruct
     * @param dtype
     * @param readable_flag
     * @param sensitive     是否需要生成敏感题
     * @param org_id
     */
    private Map<String, Object> getReportHeaderNew(ProjectDto project, QstructsDto qStruct, String dtype, Boolean readable_flag, Boolean sensitive, String org_id) {
        List<SurveyColumnVo> columns = surveyGetOrCreateColumn(project, qStruct.getProjectId(), null, org_id);
        Map<Integer, List<String>> ctype_columns = new HashMap<>();
        columns.forEach(v -> ctype_columns.compute(v.getCtype(), (k, s) -> {
            if (Objects.isNull(s)) {
                s = new ArrayList<>();
            }
            if (StrUtil.isNotEmpty(org_id)) {
                s.add(!v.getCtype().equals(COLUMN_CTYPE_EXTERNAL.getNumber()) ? v.getCol() : v.getColDesc());
            } else {
                s.add(v.getCol());
            }
            return s;
        }));
        List<String> q_gid_headers = new ArrayList<>();
        List<String> q_readable_headers = new ArrayList<>();
        List<String> q_readable_headers_v2 = new ArrayList<>();
        Map sensitive_dict = new HashMap(4);
        sensitive_dict.put("01", new HashMap<>());
        sensitive_dict.put("auto_table_gids", new ArrayList<>());
        if (JSONObject.parseObject(qStruct.getData()).isEmpty()) {
            Map<String, Object> data = new HashMap(16);
            data.put("header_list", new ArrayList<>());
            data.put("qdict", new HashMap<>());
            data.put("question_list", new ArrayList<>());
            data.put("readable_headers", new ArrayList<>());
            data.put("sensitive_dict", new ArrayList<>());
            data.put("fixed_header", new ArrayList<>());
            data.put("q_gid_headers", new ArrayList<>());
            data.put("ctype_columns", new HashMap<>());
            data.put("auto_table_title_dict", new HashMap<>());
            return data;
        }
        List<JSONObject> question_list = new ArrayList<>();
        getQstructQuestions(JSONObject.parseObject(qStruct.getData()), new HashMap<>(), question_list);
        //获取表头数据
        Map<String, JSONObject> qdict = writeHeaders(question_list, qStruct, dtype, readable_flag, q_gid_headers, sensitive, sensitive_dict, q_readable_headers, q_readable_headers_v2);
        List<String> header_list = new ArrayList();
        List<String> fixed_header = new ArrayList();
        List<String> readable_headers = new ArrayList();
        List<String> readable_headers_v2 = new ArrayList();
        Map<String, String> auto_table_title_dict = new HashMap();
        COLUMNS_META_SORT.forEach(v -> {
            if (v == COLUMN_CTYPE_QUESTION.getNumber()) {
                header_list.addAll(q_gid_headers);
                //第一行表头-题目
                readable_headers.addAll(q_readable_headers);
                readable_headers_v2.addAll(q_readable_headers_v2);
                int min = Math.min(q_readable_headers.size(), q_readable_headers_v2.size());
                for (int i = 0; i < min; i++) {
                    auto_table_title_dict.put(q_readable_headers.get(i), q_readable_headers_v2.get(i));
                }
            } else if (ctype_columns.containsKey(v)) {
                List<String> list = ctype_columns.get(v);
                //01 编码列名
                header_list.addAll(list);
                //固定列: 包含元数据，联系人数据，扩展字段数据等 除了问题字段以外的数据
                fixed_header.addAll(list);
                //可读数据列名
                readable_headers.addAll(list);
                readable_headers_v2.addAll(list);
            }
        });
        Map<String, Object> data = new HashMap(16);
        data.put("header_list", header_list);
        data.put("qdict", qdict);
        data.put("question_list", question_list);
        data.put("readable_headers", readable_headers);
        data.put("readable_headers_v2", readable_headers_v2);
        data.put("sensitive_dict", sensitive_dict);
        data.put("fixed_header", fixed_header);
        data.put("q_gid_headers", q_gid_headers);
        data.put("ctype_columns", ctype_columns);
        data.put("auto_table_title_dict", auto_table_title_dict);
        return data;
    }

    private Map<String, JSONObject> writeHeaders(List<JSONObject> question_list, QstructsDto qStruct, String dtype, Boolean readable_flag, List q_gid_headers, Boolean sensitive, Map sensitive_dict, List q_readable_headers, List q_readable_headers_v2) {
        Map<String, JSONObject> qdict = new HashMap<>();
        question_list.forEach(s -> {
            BaseTypeHandler typeHandler = handlerMap.get(s.getString("qtype"));
            if (Objects.nonNull(typeHandler)) {
                Map<String, String> change_dict = new HashMap<>();
                if (readable_flag) {
                    typeHandler.getSelectColumnChangeDictV2(s, dtype).forEach((k, v) -> {
                        if (ObjectUtil.isNotEmpty(v)) {
                            change_dict.put(k, v);
                        }
                    });
                    typeHandler.getOpenColumnChangeDictV2(s).forEach((k, v) -> {
                        if (ObjectUtil.isNotEmpty(v)) {
                            change_dict.put(k, v);
                        }
                    });
                    typeHandler.getScoreColumnChangeDictV2(s, qStruct).forEach((k, v) -> {
                        if (ObjectUtil.isNotEmpty(v)) {
                            change_dict.put(k, v);
                        }
                    });
                }
                writeHeader(typeHandler.getSelectColumnListV2(s, dtype), typeHandler.getOpenColumnListV2(s), typeHandler.getScoreColumnListV2(s, qStruct), change_dict, s, q_gid_headers, sensitive, sensitive_dict, readable_flag, q_readable_headers, q_readable_headers_v2);
                setSensitiveAutotableDict(s, "auto_table_gids", sensitive, sensitive_dict);
                qdict.put(s.getString("gid"), s);
            }
        });
        return qdict;
    }

    private void setSensitiveAutotableDict(JSONObject question, String stype, Boolean sensitive, Map sensitive_dict) {
        if (Objects.isNull(sensitive)) {
            return;
        }
        if (!"auto_table".equals(question.getString("qtype"))) {
            return;
        }
        ((List<String>) sensitive_dict.get(stype)).add(question.getString("oid"));
    }

    private void writeHeader(List<String> sel_cols, List<String> open_cols, List<String> score_cols, Map change_dict, JSONObject question, List q_gid_headers, Boolean sensitive, Map sensitive_dict, Boolean readable_flag, List q_readable_headers, List q_readable_headers_v2) {
        List<String> cols = new ArrayList<>();
        cols.addAll(sel_cols);
        cols.addAll(open_cols);
        cols.addAll(score_cols);
        if (!cols.isEmpty()) {
            try {
                String qtype = question.getString("qtype");
                List<String> finalCols = cols;
                if (QTYPE1.contains(qtype)) {
                    List<String> col = new ArrayList();
                    //按照 行列 顺序 显示数据
                    List<JSONObject> rows_items = (List<JSONObject>) question.getOrDefault("rows_items", Collections.emptyList());
                    rows_items.forEach(v -> {
                        finalCols.forEach(s -> {
                            if (v.getString("oid").equals(s.split("__")[1])) {
                                col.add(s);
                            }
                        });
                    });
                } else if ("matrix_blank".equals(qtype)) {
                    //matrix_blank：矩阵填空
                    List<String> col = new ArrayList();
                    List<JSONObject> rows_items = (List<JSONObject>) question.getOrDefault("rows_items", Collections.emptyList());
                    rows_items.forEach(v -> {
                        List<String> colOpen = new ArrayList();
                        finalCols.forEach(s -> {
                            if (v.getString("oid").equals(s.split("__")[1])) {
                                if (s.indexOf("__open") < 0) {
                                    col.add(s);
                                } else {
                                    colOpen.add(s);
                                }
                            }
                        });
                        col.addAll(colOpen);
                    });
                    cols = col;
                }
            } catch (Exception e) {
                log.error("答卷数据导出 矩阵题 标题排序 索引报错，error:", e);
            }
            cols.forEach(v -> {
                if (!q_gid_headers.contains(v)) {
                    q_gid_headers.add(v);
                    setSensitiveDict(question, v, "01", sensitive, sensitive_dict);
                }
                if (!change_dict.isEmpty() && readable_flag) {
                    String sel_col_rb = (String) change_dict.getOrDefault(v, v);
                    if (!q_readable_headers.contains(sel_col_rb)) {
                        q_readable_headers.add(v);
                        q_readable_headers_v2.add(sel_col_rb);
                    }
                }
            });
        }
    }

    private void setSensitiveDict(JSONObject question, String sel_col, String stype, Boolean sensitive, Map sensitive_dict) {
        if (Objects.isNull(sensitive)) {
            return;
        }
        String oid = question.getString("oid");
        Map<String, List<String>> data = (Map<String, List<String>>) sensitive_dict.get(stype);
        List<String> value = data.get(oid);
        if (Objects.isNull(value)) {
            value = new ArrayList<>();
        }
        value.add(sel_col);
        data.put(oid, value);
    }

    /**
     * 旧问卷不存在column，则第一次进入答卷数据页面触发
     *
     * @param project
     * @param projectId
     * @param orgId
     */
    private List<SurveyColumnVo> surveyGetOrCreateColumn(ProjectDto project, String projectId, String root_group_id, String orgId) {
        List<SurveyColumnVo> columns = surveyCommonService.getColumnsNeDelete(projectId, new JSONObject(), orgId);
        if (!columns.isEmpty()) {
            return columns;
        }

        return surveyMessageReceiverService.createColumnsRelsproject(projectId, null, project.getCompanyId());
    }

    /**
     * 发布问卷时触发生成列
     * 答卷数据只保留当前版本的列，其他版本的题目列被删除 column status 设置成 COLUMN_STATUS_DELETE
     *
     * @param project
     * @param root_group_id
     */
    private List<SurveyColumnVo> createColumnsRelsproject(ProjectDto project, String root_group_id, String companyID) {
        String projectId = project.getId();
       /* if (Objects.isNull(root_group_id)) {
            OrgInfoDto organization = userGrpcClient.getOrganization(companyID);
            root_group_id = organization.getRootGroupId();
            if (StrUtil.isEmpty(root_group_id)) {
                throw new BusinessException(String.format("get_organization failed, companyID={%s}')", root_group_id));
            }
        }*/
        createMetaColumnsFront(projectId, root_group_id, companyID);
        surveyMessageReceiverService.createQColumns(projectId);
        createMetaColumnsBehind(projectId);
        //创建总分字段
        createScoreColumns(project);
        createMemberColumns(project.getId());
        //创建外部参数字段
        createExtParamsColumns(project.getId());
        return surveyCommonService.getColumnsNeDelete(projectId, new JSONObject(), null);
    }

    /**
     * 获取最新的外部参数
     *
     * @param id
     */
    @Override
    public void createExtParamsColumns(String id) {
        //获取最新的外部参数
        SurveyExtParameter extParam = surveyExtParameterService.getExtParam(id, null, null);
        Map<String, JSONObject> ext_params_map = new HashMap();
        //获取外部参数
        List<JSONObject> extParamData;
        if (Objects.nonNull(extParam) && CollUtil.isNotEmpty(extParamData = JSONArray.parseArray(extParam.getData(), JSONObject.class))) {
            extParamData.forEach(v -> ext_params_map.put(v.getString("name"), v));
        }

        // 外部参数改版，列里面要加进来新的外部参数数据
        // 获取问卷下所有投放使用过的外部参数
        JSONObject query = new JSONObject();
        query.put("survey_id", id);
        List<DeliverExtParamsDto> deliver_ext_param_list = deliverGrpcClient.listDeliverExtparams(query);
        deliver_ext_param_list.forEach(v -> {
            JSONObject data = new JSONObject(4);
            data.put("name", v.getExtparamName());
            data.put("dtype", v.getExtparamType());
            ext_params_map.put(v.getExtparamName(), data);
        });

        //获取所有已有的外部参数列
        AtomicInteger sort_no = new AtomicInteger();
        LambdaQueryWrapper<SurveyColumn> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SurveyColumn::getProjectId, id).eq(SurveyColumn::getCtype, COLUMN_CTYPE_EXTERNAL.getNumber());
        wrapper.orderByAsc(SurveyColumn::getCtype);
        wrapper.orderByAsc(SurveyColumn::getSortNo);
        List<SurveyColumn> columnList = surveyColumnMapper.selectList(wrapper);
        columnList.forEach(column -> {
            SurveyColumn surveyColumn = new SurveyColumn();
            surveyColumn.setId(column.getId());
            if (ext_params_map.containsKey(column.getCol())) {
                //之前存在的数据列，并且现在还存在，更新一下
                JSONObject ext_param = ext_params_map.remove(column.getCol());
                surveyColumn.setCol(ext_param.getString("name"));
                surveyColumn.setStatus(column.getStatus().equals(COLUMN_STATUS_DELETE.getNumber()) ? COLUMN_STATUS_HIDE.getNumber() : column.getStatus());
                surveyColumn.setSortNo(sort_no.get());
            } else {
                //之前存在的数据列，但是现在不存在，修改一下状态为删除
                surveyColumn.setStatus(COLUMN_STATUS_DELETE.getNumber());
            }
            surveyColumnMapper.updateById(surveyColumn);
            sort_no.addAndGet(1);
        });
        if (CollUtil.isNotEmpty(ext_params_map)) {
            List<SurveyColumn> data = new ArrayList<>(ext_params_map.size());
            //当前列中不存在的数据，要新建
            ext_params_map.keySet().forEach(k -> {
                SurveyColumn column = new SurveyColumn();
                column.setCtype(COLUMN_CTYPE_EXTERNAL.getNumber());
                column.setProjectId(id);
                column.setCol(k);
                column.setSortNo(sort_no.get());
                column.setStatus(COLUMN_STATUS_HIDE.getNumber());
                data.add(column);
                sort_no.addAndGet(1);
            });
            surveyColumnMapper.insertBatchSomeColumn(data);
        }
    }

    /**
     * 联系人
     *
     * @param id
     */
    @Override
    public void createMemberColumns(String id) {
        LambdaQueryWrapper<SurveyColumn> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SurveyColumn::getProjectId, id);
        queryWrapper.eq(SurveyColumn::getCtype, COLUMN_CTYPE_MEMBER.getNumber());
        queryWrapper.ne(SurveyColumn::getStatus, COLUMN_STATUS_DELETE.getNumber());
        List<SurveyColumn> surveyColumnsList = surveyColumnMapper.selectList(queryWrapper);
        Map<String, SurveyColumn> col_columns_map = new HashMap();
        surveyColumnsList.forEach(v -> memberColumnProcess(col_columns_map, v));
        Integer sort_no = 0;
        List<String> metas = new ArrayList();
        metas.addAll(COLUMNS_MEMBER);
        for (String v : metas) {
            //存在修改sort_no , 不存在就新建
            SurveyColumn surveyColumn;
            if (col_columns_map.containsKey(v) && (surveyColumn = col_columns_map.get(v)).getStatus() != COLUMN_STATUS_DELETE.getNumber()) {
                surveyColumn.setSortNo(sort_no);
                sort_no += 1;
                surveyColumnMapper.updateById(surveyColumn);
            } else {
                sort_no = findOneAndUpdateColumns(id, COLUMN_CTYPE_MEMBER.getNumber(), v, sort_no, COLUMN_STATUS_HIDE.getNumber());
            }
        }
    }

    private void memberColumnProcess(Map<String, SurveyColumn> col_columns_map, SurveyColumn member_column) {
        col_columns_map.put(member_column.getCol(), member_column);
        //错误的列删掉
        if (!COLUMNS_MEMBER.contains(member_column.getCol())) {
            member_column.setStatus(COLUMN_STATUS_DELETE.getNumber());
            surveyColumnMapper.updateById(member_column);
        }
    }

    private void createScoreColumns(ProjectDto project) {
        String id = project.getId();
        if (project.getReleaseVer() <= 0) {
            return;
        }
        //判断是否有总分字段
        boolean exists_score = checkSurveyExistsScore(id, project.getReleaseVer());
        LambdaQueryWrapper<SurveyColumn> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SurveyColumn::getProjectId, id);
        wrapper.eq(SurveyColumn::getCtype, COLUMN_CTYPE_SCORE.getNumber());
        //已存在总分字段不需要处理
        SurveyColumn surveyColumn = surveyColumnMapper.selectOne(wrapper);
        Integer status = surveyColumn.getStatus();
        if (Objects.nonNull(surveyColumn)) {
            if (exists_score) {
                if (status == COLUMN_STATUS_DELETE.getNumber()) {
                    //存在总分字段, 显示总分字段
                    surveyColumn.setStatus(COLUMN_STATUS_HIDE.getNumber());
                    //await column.save()
                    SurveyColumn column = new SurveyColumn();
                    column.setId(surveyColumn.getId());
                    column.setStatus(surveyColumn.getStatus());
                    surveyColumnMapper.updateById(column);
                }
            } else {
                if (status != COLUMN_STATUS_DELETE.getNumber()) {
                    //存在总分字段, 显示总分字段
                    surveyColumn.setStatus(COLUMN_STATUS_DELETE.getNumber());
                    //await column.save()
                    SurveyColumn column = new SurveyColumn();
                    column.setId(surveyColumn.getId());
                    column.setStatus(surveyColumn.getStatus());
                    surveyColumnMapper.updateById(column);
                }
            }
        } else {
            if (exists_score) {
                //不存在总分字段创建
                SurveyColumn column = new SurveyColumn();
                column.setCol("总分");
                column.setProjectId(id);
                column.setCtype(COLUMN_CTYPE_SCORE.getNumber());
                column.setSortNo(1);
                column.setStatus(COLUMN_STATUS_HIDE.getNumber());
                surveyColumnMapper.insert(column);
            }
        }
    }

    /**
     * 检查项目是否存在评分题
     *
     * @param id
     * @param version
     */
    @Override
    public boolean checkSurveyExistsScore(String id, int version) {
        List<QstructsDto> qstructsDtos = qdesGrpcClient.listQStruct(id, version);
        for (QstructsDto s : qstructsDtos) {
            //{"2":[{"o_gid":"3","r_gid":"5","score":"10"},{"o_gid":"3","r_gid":"6","score":"30"}]}
            JSONObject scores = JSONObject.parseObject(s.getScores());
            if (ObjectUtils.isEmpty(scores)) {
                return false;
            }

            JSONObject data = JSONObject.parseObject(s.getData());
            Set<String> exists_gid = surveyColumnService.getQstructItemsGids(data);

            if (ObjectUtils.isEmpty(scores)) {
                return false;
            }

            for (String key : scores.keySet()) {
                if (exists_gid.contains(key)) {
                    JSONArray jsonArray = scores.getJSONArray(key);
                    for (int i = 0; i < jsonArray.size(); i++) {
                        JSONObject item = jsonArray.getJSONObject(i);
                        if (item.getIntValue("score") > 0) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    /**
     * 问题列，处理问卷所有版本列
     * 问卷发布需要执行所有题目的版本，因为要对题目对排序，重新设置sort_no
     */
/*    private void createQColumns(ProjectDto project) {
        if (project.getReleaseVer() <= 0) {
            return;
        }
        int versions = project.getReleaseVer();
        List<QStruct> qstruct_list = qdesGrpcClient.qdesListQstructWithq(project.getId(), versions);
        if (qstruct_list.isEmpty()) {
            throw new BusinessException("cannot find qstruct");
        }
        Set columns_set = new HashSet();
        QStruct qStruct = qstruct_list.get(0);
        if (StrUtil.isNotEmpty(qStruct.getData())) {
            List process = new ArrayList();
            Integer sort_no = 0;
            List<JSONObject> questionsList = surveyCommonService.walkQstructQuestions(JSONObject.parseObject(qStruct.getData()));
            for (JSONObject v : questionsList) {
                Map map = new HashMap(4);
                map.put("columns_set", new HashSet<>());
                map.put("sort_no", sort_no);
                questionUtilsFunc(v, map, columns_set, qStruct);
                sort_no += 1000;
            }
            //把其他版本的column status 设置成 COLUMN_STATUS_DELETE
            LambdaQueryWrapper<SurveyColumn> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.notIn(SurveyColumn::getId, columns_set);
            queryWrapper.eq(SurveyColumn::getProjectId, project.getId());
            queryWrapper.eq(SurveyColumn::getCtype, COLUMN_CTYPE_QUESTION.getNumber());
            Long column_count = surveyColumnMapper.selectCount(queryWrapper);
            if (column_count > 0) {
                queryWrapper.select(SurveyColumn::getId);
                queryWrapper.groupBy(SurveyColumn::getId);
                surveyColumnMapper.selectList(queryWrapper);
                SurveyColumn surveyColumn = new SurveyColumn();
                surveyColumn.setStatus(COLUMN_STATUS_DELETE.getNumber());
                surveyColumnMapper.update(surveyColumn, queryWrapper);
            }
        }
    }
    private void questionUtilsFunc(JSONObject v, Map map, Set columns_set, QStruct qStruct) {
        columns_set.addAll((Collection) map.get("columns_set"));
    }*/

    /**
     * 前半部分元数据
     *
     * @param root_group_id
     */
    private void createMetaColumnsFront(String projectId, String root_group_id, String companyID) {
        LambdaQueryWrapper<SurveyColumn> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SurveyColumn::getProjectId, projectId);
        queryWrapper.eq(SurveyColumn::getCol, COLUMNS_META_FRONT.get(0));
        queryWrapper.eq(SurveyColumn::getCtype, COLUMN_CTYPE_META_FRONT.getNumber());
        queryWrapper.ne(SurveyColumn::getStatus, COLUMN_STATUS_DELETE.getNumber());
        List<SurveyColumn> surveyColumnsList = surveyColumnMapper.selectList(queryWrapper);
        Map<String, SurveyColumn> col_columns_map = new HashMap();
        if (!surveyColumnsList.isEmpty()) {
            surveyColumnsList.forEach(v -> frontColumnProcess(col_columns_map, v));
        }
        int sort_no = 0;
        List<String> metas = new ArrayList<>(COLUMNS_META_FRONT);
        if (/*StrUtil.isNotEmpty(root_group_id) && */StrUtil.isNotEmpty(companyID)) {
            List<Map<String, String>> grouplevels = userGrpcClient.getGrouplevels(companyID);
            metas.addAll(grouplevels.stream().map(v -> v.get("title")).collect(Collectors.toList()));
            for (String v : metas) {
                //存在修改sort_no , 不存在就新建
                SurveyColumn surveyColumn;
                if (col_columns_map.containsKey(v) && (surveyColumn = col_columns_map.get(v)).getStatus() != COLUMN_STATUS_DELETE.getNumber()) {
                    surveyColumn.setSortNo(sort_no);
                    sort_no += 1;
                    surveyColumnMapper.updateById(surveyColumn);
                } else {
                    sort_no = findOneAndUpdateColumns(projectId, COLUMN_CTYPE_META_FRONT.getNumber(), v, sort_no, null);
                }
            }
        }
    }


    /**
     * 后半部分元数据
     *
     * @param projectId
     */
    private void createMetaColumnsBehind(String projectId) {
        LambdaQueryWrapper<SurveyColumn> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SurveyColumn::getProjectId, projectId);
        queryWrapper.eq(SurveyColumn::getCtype, COLUMN_CTYPE_META_BEHIND.getNumber());
        queryWrapper.ne(SurveyColumn::getStatus, COLUMN_STATUS_DELETE.getNumber());
        List<SurveyColumn> surveyColumnsList = surveyColumnMapper.selectList(queryWrapper);
        Map<String, SurveyColumn> col_columns_map = new HashMap();
        List delete_status_ids = new ArrayList();
        if (!surveyColumnsList.isEmpty()) {
            surveyColumnsList.forEach(v -> behindColumnProcess(col_columns_map, v, delete_status_ids));
        }
        if (!delete_status_ids.isEmpty()) {
            LambdaUpdateWrapper<SurveyColumn> wrapper = new LambdaUpdateWrapper<>();
            wrapper.in(SurveyColumn::getId, delete_status_ids);
            wrapper.set(SurveyColumn::getStatus, COLUMN_STATUS_DELETE.getNumber());
            surveyColumnMapper.update(null, wrapper);
        }
        Integer sort_no = 0;
        List<String> metas = COLUMNS_META_BEHIND;
        for (String v : metas) {
            //存在修改sort_no , 不存在就新建
            SurveyColumn surveyColumn;
            if (col_columns_map.containsKey(v) && (surveyColumn = col_columns_map.get(v)).getStatus() != COLUMN_STATUS_DELETE.getNumber()) {
                surveyColumn.setSortNo(sort_no);
                sort_no += 1;
                SurveyColumn column = new SurveyColumn();
                column.setId(surveyColumn.getId());
                column.setSortNo(sort_no);
                surveyColumnMapper.updateById(column);
                continue;
            }
            if (COLUMNS_META.contains(v)) {
                sort_no = findOneAndUpdateColumns(projectId, COLUMN_CTYPE_META_BEHIND.getNumber(), v, sort_no, null);
            } else {
                sort_no = findOneAndUpdateColumns(projectId, COLUMN_CTYPE_META_BEHIND.getNumber(), v, sort_no, COLUMN_STATUS_HIDE.getNumber());
            }
        }
    }

    private void behindColumnProcess(Map<String, SurveyColumn> col_columns_map, SurveyColumn behind_column, List delete_status_ids) {
        col_columns_map.put(behind_column.getCol(), behind_column);
        if (!COLUMNS_META_BEHIND.contains(behind_column.getCol())) {
            behind_column.setStatus(COLUMN_STATUS_DELETE.getNumber());
            delete_status_ids.add(behind_column.getId());
        }
    }

    private Integer findOneAndUpdateColumns(String projectId, Integer ctype, String meta, Integer sort_no, Integer status) {
        LambdaQueryWrapper<SurveyColumn> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SurveyColumn::getProjectId, projectId).eq(SurveyColumn::getCtype, ctype).eq(SurveyColumn::getCol, meta).ne(SurveyColumn::getStatus, COLUMN_STATUS_DELETE.getNumber());
        SurveyColumn surveyColumn = surveyColumnMapper.selectOne(queryWrapper);
        if (ObjectUtil.isNotEmpty(surveyColumn)) {
            surveyColumnMapper.update(null, new LambdaUpdateWrapper<SurveyColumn>().eq(SurveyColumn::getProjectId, projectId).eq(SurveyColumn::getCtype, ctype).eq(SurveyColumn::getCol, meta).ne(SurveyColumn::getStatus, COLUMN_STATUS_DELETE.getNumber()).set(Objects.nonNull(status), SurveyColumn::getStatus, status).set(SurveyColumn::getSortNo, sort_no));
        } else {
            SurveyColumn column = new SurveyColumn();
            column.setProjectId(projectId);
            column.setCtype(ctype);
            column.setCol(meta);
            column.setSortNo(sort_no);
            column.setQstructIds("[]");
            column.setCustomAttr("{}");
            column.setQtype(COLUMN_QTYPE_QUESTION_VALUE);
            if (ObjectUtil.isNotEmpty(status)) {
                column.setStatus(status);
            } else {
                column.setStatus(COLUMN_STATUS_SHOW_VALUE);
            }
            surveyColumnMapper.insert(column);
        }

        sort_no += 1;
        return sort_no;
    }

    private void frontColumnProcess(Map col_columns_map, SurveyColumn front_column) {
        col_columns_map.put(front_column.getCol(), front_column);
        if (!COLUMNS_META_FRONT.contains(front_column.getCol())) {
            front_column.setStatus(COLUMN_STATUS_DELETE.getNumber());
            surveyColumnMapper.updateById(front_column);
        }
    }

    /**
     * 设置进度条状态
     *
     * @param operation      参考 PROGRESS_BAR_OPERATION_MAP
     * @param key            唯一标识
     * @param complete_count 已完成条数
     * @param total_count    总条数
     * @param timeout        redis过期时间默认3600s
     */
    private void setProgressBarStatus(int operation, String key, long complete_count, long total_count, long timeout) {
        if (PROGRESS_BAR_OPERATION_MAP.containsKey(operation)) {
            return;
        }
        //last_timestamp 用来记录上一次更新的数据，可用来判断导入导出是否超时，超时可以更新状态
        key = String.format("{PROGRESS_BAR_OPERATION_MAP[%s]}.{str(%s)}", operation, key);
        JSONObject count_str = new JSONObject(8);
        count_str.put("complete_count", complete_count);
        count_str.put("total_count", total_count);
        count_str.put("last_timestamp", LocalDateTime.now());
        redisService.set(key, count_str.toJSONString(), timeout, TimeUnit.SECONDS);
    }

    /**
     * 计算更新进度步长
     *
     * @param memberCount 数据总条数
     * @return
     */
    public int calcUpdateProgressStep(long memberCount) {
        //同步更新进度步长 5% 更新一次
        int per = (int) (memberCount * 0.05);
        //同步更新进度步长最小
        return Math.max(per, 50);
    }

    private String createExportRecord(String pid, String dtype, Map filemap, String userId, JSONObject param) {
        //创建导出记录
        SurveyRespondentExportRecord exportRecord = new SurveyRespondentExportRecord();
        exportRecord.setProjectId(pid);
        exportRecord.setFileName((String) filemap.get("fileName"));
        exportRecord.setCreatedId(userId);
        exportRecord.setFileType(getRecordTypeByDtype(dtype));
        exportRecord.setStatus(RESPONDENTEXPORTRECORD_STATUS_WAITING.getNumber());
        baseMapper.insert(exportRecord);
        String record_id = exportRecord.getId();
        //设置key 用于开放平台同步数据
        if (param.containsKey("_source")) {
            redisService.set(String.format("OP:DATA_EXPORT_{" + param.getString("_source") + "}"), record_id);
        }
        return record_id;
    }

    private Integer getRecordTypeByDtype(String dtype) {
        switch (dtype) {
            case "01":
                return RESPONDENTEXPORTRECORD_FILE_TYPE_01.getNumber();
            case "not01":
                return RESPONDENTEXPORTRECORD_FILE_TYPE_NOT01.getNumber();
            case "anothernot01":
                return RESPONDENTEXPORTRECORD_FILE_TYPE_ANOTHERNOT01.getNumber();
            case "readable":
                return RESPONDENTEXPORTRECORD_FILE_TYPE_READABLE.getNumber();
            case "01_spss_label":
                return RESPONDENTEXPORTRECORD_FILE_TYPE_SPSS_LABELS_01.getNumber();
            case "not01_spss_label":
                return RESPONDENTEXPORTRECORD_FILE_TYPE_SPSS_LABELS_NOT01.getNumber();
            case "01_multicode":
                return RESPONDENTEXPORTRECORD_FILE_TYPE_MULTICODE_01.getNumber();
            case "not01_multicode":
                return RESPONDENTEXPORTRECORD_FILE_TYPE_MULTICODE_NOT01.getNumber();
            case "spss_code":
                return RESPONDENTEXPORTRECORD_FILE_TYPE_SPSS_CODE.getNumber();
            case "spss_data":
                return RESPONDENTEXPORTRECORD_FILE_TYPE_SPSS.getNumber();
            case "rspd_attachment":
                return RESPONDENTEXPORTRECORD_FILE_TYPE_ATTACHMENT.getNumber();
        }
        if (Objects.nonNull(RespondentExportRecordStatus.valueOf(dtype))) {
            return Integer.valueOf(dtype);
        }
        throw new BusinessException("dtype 参数值错误");
    }

    private boolean setExportTaskLock(String pid, String dtype, String uid) {
        //检查并设置锁
        String key = gen_lock_key(pid, dtype, uid);
        return redisService.set(key, key, 6, TimeUnit.MINUTES);
    }

    private String gen_lock_key(String pid, String dtype, String uid) {
        //生成lock_key, 每个用户的每种类型添加锁
        return String.format("survey:rspd_export.{%s}.{%s}.{%s}", pid, dtype, uid);
    }

    /*private List<TranslationLanguage> listTranslationlanguage(String id, String language_code) {
        return qdesGrpcClient.listTranslationlanguage(id, language_code);
    }*/

    /**
     * 返回进度条百分比
     *
     * @param param
     * @return
     */
    @Override
    public Map<String, Object> rspdExportProgress(JSONObject param) {
        Map<String, Object> data = new HashMap();
        //验证参数
        checkQueryParam(param);
        Object record_id = param.get("id");
        if (record_id instanceof List) {
            List<Double> res = new ArrayList();
            for (String id : (List<String>) record_id) {
                Double percent = null;
                if (StrUtil.isNotEmpty(id)) {
                    percent = getProgress(id);
                }
                res.add(percent);
            }
            data.put("percent", res);
        } else if (StrUtil.isNotEmpty((CharSequence) record_id)) {
            // 验证record_id是否有值
            data.put("percent", getProgress((String) record_id));
        } else {
            data.put("percent", null);
        }
        return data;
    }

    private Double getProgress(String id) {
        Object count_dict = getProgressBarStatus(SURVEY_RESPONDENT_EXPORT, id);
        if (ObjectUtil.isEmpty(count_dict)) {
            //redis 中没有进度的数据
            return redisNotExistsParamCalcProgress(id);
        } else {
            return redisExistsParamCalcProgress(count_dict, id);
        }
    }

    /**
     * 使用redis中存储参数计算当前进度
     *
     * @param count_dict_value
     * @param record_id
     */
    private Double redisExistsParamCalcProgress(Object count_dict_value, String record_id) {
        JSONObject count_dict = (JSONObject) count_dict_value;
        Integer complete_count = count_dict.getInteger("complete_count");
        Integer total_count = count_dict.getInteger("total_count");
        Long last_timestamp = count_dict.getLong("last_timestamp");
        //次更新数据的状态超过3600s 则设置状态为导入超时
        if (System.currentTimeMillis() - last_timestamp > 3600) {
            setOverTimeRecordStatus(record_id);
            return 1.0;
        }
        if (total_count == 0) {
            return 1.0;
        }
        return (double) complete_count / (double) total_count;
    }

    /**
     * 超时时更新记录状态
     *
     * @param record_id
     */
    private void setOverTimeRecordStatus(String record_id) {
        //导出超时时更新导入记录状态为导入失败
        log.info(String.format("surveyRespondent 导出超时时更新状态为导入失败 export_record_id %s", record_id));
        //先更新导出状态为失败
        SurveyRespondentExportRecord surveyRespondentExportRecord = new SurveyRespondentExportRecord();
        surveyRespondentExportRecord.setId(record_id);
        surveyRespondentExportRecord.setStatus(RESPONDENTEXPORTRECORD_STATUS_FAILED.getNumber());
        baseMapper.updateById(surveyRespondentExportRecord);
    }

    /**
     * redis中不存在数据时计算方式
     *
     * @param record_id
     */
    private Double redisNotExistsParamCalcProgress(String record_id) {
        int status = getRecordStatus(record_id);
        //状态为成功或者失败均返回100%
        if (status == ExportStatusEnum.SUCCESS.getIndex() || status == ExportStatusEnum.FAILED.getIndex()) {
            return 1.0;
        }
        return 0.0;
    }

    /**
     * 获取导入导入出记录状态
     *
     * @param record_id
     */
    private int getRecordStatus(String record_id) {
        SurveyRespondentExportRecord exportRecord = baseMapper.selectById(record_id);
        if (Objects.isNull(exportRecord)) {
            throw new BusinessException("资源不存在");
        }
        if (RESPONDENTEXPORTRECORD_STATUS_SUCESS.getNumber() == exportRecord.getStatus()) {
            return ExportStatusEnum.SUCCESS.getIndex();
        } else if (RESPONDENTEXPORTRECORD_STATUS_FAILED.getNumber() == exportRecord.getStatus()) {
            return ExportStatusEnum.FAILED.getIndex();
        } else if (RESPONDENTEXPORTRECORD_STATUS_WAITING.getNumber() == exportRecord.getStatus()) {
            return ExportStatusEnum.WAITING.getIndex();
        }
        return ExportStatusEnum.PROCESSING.getIndex();
    }

    /**
     * 获取进度条状态
     *
     * @param operation 参考 PROGRESS_BAR_OPERATION_MAP
     * @param key       唯一标识
     */
    private Object getProgressBarStatus(Integer operation, String key) {
        if (!PROGRESS_BAR_OPERATION_MAP.containsKey(operation)) {
            return null;
        }
        key = String.format("%s.%s", PROGRESS_BAR_OPERATION_MAP.get(operation), key);
        String res = (String) redisService.get(key);
        if (StrUtil.isNotEmpty(res)) {
            return JSONObject.parseObject(res);
        }
        return res;
    }

    private void checkQueryParam(JSONObject param) {
        if (ObjectUtil.isEmpty(param.get("id"))) {
            throw new BusinessException("缺少id");
        }
    }

    @Override
    public ServiceResult<Object> respondentsV12Attachment(String projectId, String seq) {
        String userId = SecurityContextHolder.getUserId();
        String orgId = SecurityContextHolder.getOrgId();
        SurveyRespondent surveyRespondent = getRespondentBySeq(projectId, seq);
        if (Objects.isNull(surveyRespondent)) {
            throw new BusinessException("Respondent[seq=%s] not found", seq);
        }
        Project project = qdesGrpcClient.getProject(surveyRespondent.getProjectId());
        if (Objects.isNull(project)) {
            throw new BusinessException("Project[id=%s] not found", surveyRespondent.getProjectId());
        }
        checkOrg(project.getCompanyId(), orgId);
        //String sys_id = getSystemIdByCode(appName, "survey");
        Map fileMap = new HashMap<>();
        fileMap.put("sysID", "xm");
        fileMap.put("projectID", projectId);
        String name = String.format("%s-%s.zip", seq, LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
        fileMap.put("fileName", name);
        String dtype = "rspd_attachment";
        //创建导出记录
        String record_id = createExportRecord(project.getId(), dtype, fileMap, userId, new JSONObject());
        SurveyRespondentExportRecord exportRecord = new SurveyRespondentExportRecord();
        exportRecord.setId(record_id);
        String task_id = UUID.randomUUID().toString();
        //导出记录中增加task_id
        exportRecord.setTaskId(task_id);
        //异步导出答卷附件
//        CompletableFuture.runAsync(() -> {
//            try {
//                asyncExportRspdAttachment(project.getId(), seq, record_id, dtype, name);
//            } catch (Exception e) {
//                log.error("创建导出附件数据任务失败. error:", e);
//                exportRecord.setStatus(RESPONDENTEXPORTRECORD_STATUS_FAILED.getNumber());
//            } finally {
//                baseMapper.updateById(exportRecord);
//            }
//        }, executor);
        CompletableFuture.runAsync(() -> asyncExportRspdAttachment(project.getId(), seq, record_id, dtype, name), executor)
                .exceptionally(ex -> {
                    log.error("创建导出附件数据任务失败", ex);
                    exportRecord.setStatus(RESPONDENTEXPORTRECORD_STATUS_FAILED.getNumber());
                    throw new RuntimeException("创建导出附件数据任务失败" + ex.getLocalizedMessage());
                }).whenComplete((unused, throwable) -> baseMapper.updateById(exportRecord));
        return ServiceResult.success();
    }

    public void asyncExportRspdAttachment(String pid, String seq, String record_id, String dtype, String fileName) {
        List<SurveyRespondentAttachment> attachments = getRespondentattachmentList(pid, seq);
        if (attachments.isEmpty()) {
            throw new BusinessException("respondentattachment[seq=" + seq + "] not found");
        }
        long total_count = attachments.size();
        File zipFile = zipRspdAttachment(pid, seq, attachments, total_count, record_id, fileName);
        //String file_url = ossService.uploadSimpleFile(zipFile, fileName, OssPathEnum.TEMP_SURVEY, 0, null, "答卷数据-附件导出");
        String file_url = storageService.upload(zipFile, fileName, OssPathEnum.TEMP_SURVEY.getPath(), new Dict()).getUrl();

        SurveyRespondentExportRecord exportRecord = new SurveyRespondentExportRecord();
        exportRecord.setId(record_id);
        exportRecord.setStatus(RESPONDENTEXPORTRECORD_STATUS_SUCESS.getNumber());
        exportRecord.setFileSize(String.format("%.2f", zipFile.length() / 1024.0) + "k");
        exportRecord.setFilePath(file_url);
        baseMapper.updateById(exportRecord);
        //上传完后删除本地文件
        zipFile.delete();
        //文件保存成功后在把进度设置成100%，设置进度条结束值
        setProgressBarStatus(SURVEY_RESPONDENT_EXPORT, record_id, total_count, total_count, 3700);
        log.info("export rspd_attachment data {}", file_url);
    }

    /**
     * 每份答卷的附件打包为一个zip压缩包，默认文件名使用答卷编号。
     * 在压缩包内，有附件的题目，每个题目一个文件夹，文件夹名称：#题号#-#题目名称#。
     * 每个题目文件夹内，为该题目里上传的附件。如果附件重名，则在附件原名后加上英文格式的括号和数字。
     *
     * @param project_id
     * @param seq
     * @param attachments
     * @param total_count
     * @param record_id
     * @param fileName
     */
    private File zipRspdAttachment(String project_id, String seq, List<SurveyRespondentAttachment> attachments, long total_count, String record_id, String fileName) {
        total_count = total_count * 2;
        int per = calcUpdateProgressStep(total_count);
        //设置进度条初始值
        setProgressBarStatus(SURVEY_RESPONDENT_EXPORT, record_id, 0, total_count, 3700);
        String file_dir = getAttachmentDir(project_id, seq);
        List<String> filepaths = new ArrayList();
        Map<String, Integer> filepath_count = new HashMap();
        Map<String, Map<Integer, String>> dictMap = genGidDict(project_id, seq);
        Map<Integer, String> qgid_dict = dictMap.get("qgid_dict");
        Map<Integer, String> ogid_dict = dictMap.get("ogid_dict");
        for (int row = 0; row < attachments.size(); row++) {
            SurveyRespondentAttachment att = attachments.get(row);
            String final_dir = getAttachmentDirGid(file_dir, qgid_dict.get(att.getQid()));
            if (Objects.nonNull(att.getOptionId()) && att.getOptionId() != 0) {
                final_dir = getAttachmentDirGid(file_dir, ogid_dict.get(att.getOptionId()));
            }
            String name = att.getName();
            String[] split = name.split("\\.");
            att.setName(name.length() < 50 ? name : name.substring(0, 40) + "(...)." + split[split.length - 1]);
            String filepath = new File(final_dir, att.getName()).getPath();
            filepath_count.put(filepath, filepath_count.containsKey(filepath) ? filepath_count.get(filepath) + 1 : 0);
            if (!new File(filepath).exists()) {
                downloadFile(att, filepath);
            } else if (filepaths.contains(filepath)) {
                /*List<String> filepath_st = new ArrayList(Arrays.asList(filepath.split("\\.")));
                filepath_st.add(filepath_st.size() - 1, String.format("(%s).", filepath_count.get(filepath) + 1));
                filepath = StrUtil.join("", filepath_st);*/
                String[] filepathArray = filepath.split("\\.");
                String value = filepathArray[filepathArray.length - 1];
                int suffix_len = value.length();
                filepath = String.format("%s(%s).%s", filepath.substring(0, filepath.length() - suffix_len - 1), filepath_count.get(filepath) + 1, value);
                //如果文件名重复，则在文件名后面加英文括号+数字
                if (!new File(filepath).exists()) {
                    downloadFile(att, filepath);
                }
            }
            filepaths.add(filepath);
            if (row % per == 0) {
                //每per条更新一次进度
                setProgressBarStatus(SURVEY_RESPONDENT_EXPORT, record_id, row, total_count, 3700);
            }
        }
        String project_id_dir = new File(ROOT_FILE_PATH).getPath() + File.separator + "temp" + File.separator + "respondents" + File.separator + "attachment" + File.separator + project_id;
        File zipFile = new File(project_id_dir + "_zip" + File.separator + fileName);
        //此处python处理为循环filepaths下的每个filepath截取project_id_dir后的文件目录及这一个文件，然后压缩成一个包，每压缩一个更新redis下的进度条
        //java此处简易处理，直接压缩每个filepath截取project_id_dir后的文件目录下的seq目录的所有文件
        File[] files = filepaths.stream().map(v -> v.substring(0, project_id_dir.length() + 1 + seq.length())).map(File::new).toArray(File[]::new);
        //下面的withSrcDir参数指压缩files下的每个file为文件夹时是否携带目录
        cn.hutool.core.util.ZipUtil.zip(zipFile, true, files);
        //压缩完直接更新进度条
        setProgressBarStatus(SURVEY_RESPONDENT_EXPORT, record_id, filepaths.size(), total_count, 3700);
        return zipFile;
    }

    /**
     * 下载附件
     *
     * @param att
     * @param filepath
     */
    private void downloadFile(SurveyRespondentAttachment att, String filepath) {
        String fileUrl = att.getUrl();
        try {
            if (fileUrl.startsWith("http")) {
                storageService.downloadByUrl(fileUrl).file(filepath);
                //FileUtils.copyURLToFile(new URL(fileUrl), new File(filepath));
            } else {
                log.error("答卷附件下载失败！url:{}", fileUrl);
            }
        } catch (Exception e) {
            log.error("答卷附件下载失败！url:{}", fileUrl, e);
        }
    }

    /**
     * gid：题目gid或者选项gid
     *
     * @param file_dir
     * @param gid
     */
    private String getAttachmentDirGid(String file_dir, String gid) {
        //与项目平级创建文件目录
        File file = new File(file_dir, gid);
        file.mkdirs();
        return file.getPath();
    }

    private Map<String, Map<Integer, String>> genGidDict(String project_id, String seq) {
        SurveyRespondent surveyRespondent = getRespondentBySeq(project_id, seq);
        if (Objects.isNull(surveyRespondent)) {
            throw new BusinessException("Respondent[seq=%s] not found", seq);
        }
        List<QdesQuestionDto> qs = qdesGrpcClient.getReleasedProjectQuestions(project_id, surveyRespondent.getVersion(), null);
        if (CollUtil.isEmpty(qs)) {
            throw new BusinessException("qs[seq={" + seq + "}] not found");
        }
        Map<Integer, String> qgid_dict = new HashMap();
        Map<Integer, String> ogid_dict = new HashMap();
        qs.forEach(q -> {
            String title = String.format("%s--%s", q.getCid(), q.getTitle());
            //文件夹名称 长度限制
            qgid_dict.put(q.getGid(), title.length() < 25 ? title : title.substring(0, 22) + "...");
            (Optional.ofNullable(q.getItems()).orElse(Collections.emptyList())).forEach(option -> {
                String otitle = String.format("%s--%s", option.getGid(), option.getTitle());
                ogid_dict.put(option.getGid(), otitle.length() < 25 ? otitle : otitle.substring(0, 22) + "...");
            });
        });
        Map<String, Map<Integer, String>> res = new HashMap();
        res.put("qgid_dict", qgid_dict);
        res.put("ogid_dict", ogid_dict);
        return res;
    }


    private SurveyRespondent getRespondentBySeq(String project_id, String seq) {
        LambdaQueryWrapper<SurveyRespondent> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.eq(SurveyRespondent::getProjectId, project_id);
        queryWrapper.eq(SurveyRespondent::getSeq, seq);
        return surveyRespondentMapper.selectOne(queryWrapper);
    }

    private String getAttachmentDir(String project_id, String seq) {
        File file = new File(new File(ROOT_FILE_PATH).getPath(), "temp" + File.separator + "respondents" + File.separator + "attachment" + File.separator + project_id + File.separator + seq);
        file.mkdirs();
        return file.getPath();
    }

    private List<SurveyRespondentAttachment> getRespondentattachmentList(String pid, String seq) {
        LambdaQueryWrapper<SurveyRespondentAttachment> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SurveyRespondentAttachment::getProjectId, pid);
        queryWrapper.eq(SurveyRespondentAttachment::getSeq, Integer.valueOf(seq));
        queryWrapper.last("limit 10000");
        return surveyRespondentAttachmentMapper.selectList(queryWrapper);
    }


    @Override
    public void checkOrg(String companyId, String orgId) {
        if (StrUtil.isNotEmpty(orgId) && !orgId.equals(companyId)) {
            throw new BusinessException("no permission");
        }
    }

    /*private String getSystemIdByCode(String org_code, String code) {
        UserOuterClass.SubSystem systemByCode = userGrpcClient.getSystemByCode(org_code, code);
        return systemByCode.getId();
    }*/

    @Override
    public Map<String, Object> respondentsV1(String id, JSONObject params) {
        String project_id = params.getString("project_id");
        if (StrUtil.isNotEmpty(id)) {
            return doId(params, id, project_id);
        }
        boolean use_cache = (boolean) Optional.ofNullable(params.remove("use_cache")).orElse(true);
        String userId = SecurityContextHolder.getUserId();
        String orgId = SecurityContextHolder.getOrgId();
        JSONObject sensitive_map = surveyColumnService.getUidsSensitives(project_id, userId);
        JSONObject enum_ext_params = surveyCommonService.getEnumExtParams(orgId, null);
        Integer page = params.getInteger("page");
        Integer rowsPerPage = params.getInteger("rowsPerPage");
        if (Objects.isNull(page) && Objects.isNull(rowsPerPage)) {
            return new JSONObject();
        }
        ProjectDto project = grpcConvertMapper.toProjectDto(qdesGrpcClient.getProject(project_id));
        if (Objects.isNull(project)) {
            throw new BusinessException("Project[id={" + project_id + "}] not found");
        }
        params = getFilter(params, project);
        //List<Object> item_data = itemDataNew(project_id, params, sensitive_map, enum_ext_params);
        Map<String, Object> data = new HashMap(8);
        //筛选后的seq为空的话则代表没有匹配的数据
        if (!Collections.emptyList().equals(params.get("seq"))) {
            List<Object> item_data = itemData(project_id, params, sensitive_map, enum_ext_params);
            data.put("total_count", item_data.get(2));
            data.put("rows", item_data.get(0));
            data.put("page", item_data.get(1));
        } else {
            data.put("total_count", 0);
            data.put("rows", Collections.emptyList());
            data.put("page", 1);
        }
        List<SurveyColumnVo> columns = columnsData(project, orgId, userId);
        data.put("columns", columns);
        Map<String, Object> count_map = getRspdCount(params, use_cache);
        data.put("count_map", count_map);
        return data;
    }

    private List<SurveyColumnVo> columnsData(ProjectDto project, String orgId, String userId) {
        OrgInfoDto organization = userGrpcClient.getOrganization(orgId);
        return surveyGetOrCreateColumn(project, project.getId(), organization.getRootGroupId(), orgId);
    }

    private Map<String, Object> getRspdCount(JSONObject params, boolean use_cache) {
        //py代码可以从缓存中取
        return getRspdCountByStatus(params, use_cache);
    }

    /**
     * 获取答卷数据页面的答卷数量
     * 总计、进行中、成功完成、部分完成、被甄别、配额满、备用
     *
     * @param params
     * @param use_cache
     * @return
     */
    private Map<String, Object> getRspdCountByStatus(JSONObject params, boolean use_cache) {
        QueryWrapper<SurveyRespondent> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("status,count(status) as count");
        queryWrapper.eq("project_id", params.getString("project_id"));
        queryWrapper.eq("data_status", RESPONDENT_DATA_STATUS_FORMAL_VALUE);
        queryWrapper.in("source", RESPONDENT_FORMAL_SOURCE);
        queryWrapper.in("status", Arrays.asList(params.getString("status").split(",")));
        queryWrapper.groupBy("status");
        List<Map<String, Object>> mapList = surveyRespondentMapper.selectMaps(queryWrapper);
//        Map<Integer, Integer> data = surveyRespondentMapper.selectList(queryWrapper).stream().collect(Collectors.toMap(SurveyRespondent::getStatus, SurveyRespondent::getCount));
        Map<String, Object> data = new HashMap<>();
        AtomicReference<Long> count_all = new AtomicReference<>(0L);
        mapList.forEach(a -> {
            data.put(String.valueOf(a.get("status")), a.get("count"));
            count_all.updateAndGet(v -> v + Long.parseLong(String.valueOf(a.get("count"))));
        });
        Map<String, Object> res = new HashMap(8);
        res.put("count_all", count_all);
        res.put("count_doing", data.getOrDefault(RESPONDENT_STATUS_BEGIN.getValue().toString(), 0));
        res.put("count_finish", data.getOrDefault(RESPONDENT_STATUS_FINISH.getValue().toString(), 0));
        res.put("count_unfinish", data.getOrDefault(RESPONDENT_STATUS_UNFINISH.getValue().toString(), 0));
        res.put("count_screen_out", data.getOrDefault(RESPONDENT_STATUS_SCREEN_OUT.getValue().toString(), 0));
        res.put("count_quota_full", data.getOrDefault(RESPONDENT_STATUS_QUOTA_FULL.getValue().toString(), 0));
        res.put("count_backup", data.getOrDefault(RESPONDENT_STATUS_BACKUP.getValue().toString(), 0));
        return res;
    }

    private List<Object> itemData(String project_id, JSONObject params, JSONObject sensitive_map, JSONObject enum_ext_params) {
        Integer page = (Integer) params.getOrDefault("page", 1);
        Integer rowsPerPage = (Integer) params.getOrDefault("rowsPerPage", 10);
        Page<SurveyRespondent> pageVale = Page.of(page, rowsPerPage);
        LambdaQueryWrapper<SurveyRespondent> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(SurveyRespondent::getSource, RESPONDENT_FORMAL_SOURCE);
        //queryWrapper.eq(SurveyRespondent::getDataStatus, RESPONDENT_DATA_STATUS_FORMAL_VALUE);
        queryWrapper.eq(SurveyRespondent::getProjectId, project_id);
        //queryWrapper.in(SurveyRespondent::getStatus, Arrays.asList(params.getString("status").split(",")));
        queryWrapper.orderByDesc(SurveyRespondent::getBeginTime);
        Object qval;
        if (params.containsKey("data_status")) {
            qval = params.get("data_status");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<Integer>) qval), SurveyRespondent::getDataStatus, (List<Integer>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getDataStatus, qval);
            }
        }
        if (params.containsKey("status")) {
            qval = params.get("status");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<Integer>) qval), SurveyRespondent::getStatus, (List<Integer>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getStatus, qval);
            }
        }
        if (params.containsKey("pre_discard_status")) {
            qval = params.get("pre_discard_status");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<Integer>) qval), SurveyRespondent::getPreDiscardStatus, (List<Integer>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getPreDiscardStatus, qval);
            }
        }
        if (params.containsKey("seq")) {
            qval = params.get("seq");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<Long>) qval), SurveyRespondent::getSeq, (List<Long>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getSeq, qval);
            }
        }
        if (params.containsKey("version")) {
            qval = params.get("version");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<Integer>) qval), SurveyRespondent::getVersion, (List<Integer>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getVersion, qval);
            }
        }
        if (params.containsKey("source")) {
            qval = params.get("source");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<Integer>) qval), SurveyRespondent::getSource, (List<Integer>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getSource, qval);
            }
        }
        if (params.containsKey("client_type")) {
            qval = params.get("client_type");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<Integer>) qval), SurveyRespondent::getClientType, (List<Integer>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getClientType, Integer.valueOf((String) qval));
            }
        }
        if (params.containsKey("rspd_data_id")) {
            qval = params.get("rspd_data_id");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<String>) qval), SurveyRespondent::getRspdDataId, (List<String>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getRspdDataId, qval);
            }
        }
        if (params.containsKey("rspd_weixin_id")) {
            qval = params.get("rspd_weixin_id");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<String>) qval), SurveyRespondent::getRspdWeixinId, (List<String>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getRspdWeixinId, qval);
            }
        }
        if (params.containsKey("rspd_location_id")) {
            qval = params.get("rspd_location_id");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<String>) qval), SurveyRespondent::getRspdLocationId, (List<String>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getRspdLocationId, qval);
            }
        }
        if (params.containsKey("rspd_extra_id")) {
            qval = params.get("rspd_extra_id");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<String>) qval), SurveyRespondent::getRspdExtraId, (List<String>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getRspdExtraId, qval);
            }
        }
        if (params.containsKey("rspd_attachment_id")) {
            qval = params.get("rspd_attachment_id");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<String>) qval), SurveyRespondent::getRspdAttachmentId, (List<String>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getRspdAttachmentId, qval);
            }
        }
        if (params.containsKey("submit_seq")) {
            qval = params.get("submit_seq");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<Long>) qval), SurveyRespondent::getSubmitSeq, (List<Long>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getSubmitSeq, qval);
            }
        }
        if (params.containsKey("score")) {
            qval = params.get("score");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<Integer>) qval), SurveyRespondent::getScore, (List<Integer>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getClientType, qval);
            }
        }
        if (params.containsKey("deliver_id")) {
            qval = params.get("deliver_id");
            if (qval instanceof List) {
                queryWrapper.in(CollUtil.isNotEmpty((List<String>) qval), SurveyRespondent::getDeliverId, (List<String>) qval);
            } else {
                queryWrapper.eq(SurveyRespondent::getDeliverId, qval);
            }
        }
        Page<SurveyRespondent> surveyRespondentPage = surveyRespondentMapper.selectPage(pageVale, queryWrapper);
        List<Object> new_items = genRspdColumnInfo(project_id, surveyRespondentPage.getRecords(), sensitive_map, enum_ext_params, SecurityContextHolder.getUserId());
        List<Object> item_data = new ArrayList<>();
        item_data.add(new_items);
        item_data.add(surveyRespondentPage.getCurrent());
        item_data.add(surveyRespondentPage.getTotal());
        return item_data;
    }

    /*private List<Object> itemDataNew(String project_id, JSONObject params, JSONObject sensitive_map, JSONObject enum_ext_params) {
        Integer page = (Integer) params.getOrDefault("page", 1);
        Integer rowsPerPage = (Integer) params.getOrDefault("rowsPerPage", 10);
        Page<SurveyRespondent> pageVale = Page.of(page, rowsPerPage);
        LambdaQueryWrapper<SurveyRespondent> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(SurveyRespondent::getSource, RESPONDENT_FORMAL_SOURCE);
        queryWrapper.eq(SurveyRespondent::getDataStatus, RESPONDENT_DATA_STATUS_FORMAL_VALUE);
        queryWrapper.eq(SurveyRespondent::getProjectId, project_id);
        queryWrapper.in(SurveyRespondent::getStatus, Arrays.asList(params.getString("status").split(",")));
        queryWrapper.orderByDesc(SurveyRespondent::getBeginTime);

        Page<SurveyRespondent> surveyRespondentPage = surveyRespondentMapper.selectPage(pageVale, queryWrapper);
        List<Object> new_items = genRspdColumnInfo(project_id, surveyRespondentPage.getRecords(), sensitive_map, enum_ext_params, SecurityContextHolder.getUserId());
        List<Object> item_data = new ArrayList<>();
        item_data.add(new_items);
        item_data.add(surveyRespondentPage.getCurrent());
        item_data.add(surveyRespondentPage.getTotal());
        return item_data;
    }*/

    /**
     * 生成列对应的值
     *
     * @param project_id
     * @param items
     * @param sensitive_map
     * @param enum_ext_params
     * @param userId
     */
    private List<Object> genRspdColumnInfo(String project_id, List<SurveyRespondent> items, JSONObject sensitive_map, JSONObject enum_ext_params, String userId) {
        List<Object> new_items = new ArrayList();
        LambdaQueryWrapper<SurveyColumn> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SurveyColumn::getProjectId, project_id);
        queryWrapper.ne(SurveyColumn::getStatus, COLUMN_STATUS_DELETE.getNumber());
        queryWrapper.in(SurveyColumn::getCtype, COLUMN_CTYPE_META_FRONT.getNumber(), COLUMN_CTYPE_QUESTION.getNumber(), COLUMN_CTYPE_META_BEHIND.getNumber(), COLUMN_CTYPE_MEMBER.getNumber(), COLUMN_CTYPE_EXTERNAL.getNumber(), COLUMN_CTYPE_SCORE.getNumber());
        queryWrapper.orderByAsc(SurveyColumn::getCtype);
        queryWrapper.orderByAsc(SurveyColumn::getSortNo);
        List<SurveyColumn> cursor = surveyColumnMapper.selectList(queryWrapper);
        List<String> columns_front = new ArrayList(), columns_behind = new ArrayList(), columns_member = new ArrayList(), columns_ext_params = new ArrayList(), columns_score = new ArrayList();
        List<SurveyColumn> levels_front = new ArrayList(), columns_question = new ArrayList();
        cursor.forEach(v -> {
            if (ObjectUtil.isNotEmpty(v)) {
                Integer ctype = v.getCtype();
                if (ctype == COLUMN_CTYPE_META_FRONT.getNumber()) {
                    if (v.getSortNo() > 9) {
                        levels_front.add(v);
                    }
                    columns_front.add(v.getCol());
                } else if (ctype == COLUMN_CTYPE_QUESTION.getNumber()) {
                    columns_question.add(v);
                } else if (ctype == COLUMN_CTYPE_META_BEHIND.getNumber()) {
                    columns_behind.add(v.getCol());
                } else if (ctype == COLUMN_CTYPE_MEMBER.getNumber()) {
                    columns_member.add(v.getCol());
                } else if (ctype == COLUMN_CTYPE_EXTERNAL.getNumber()) {
                    columns_ext_params.add(v.getCol());
                } else if (ctype == COLUMN_CTYPE_SCORE.getNumber()) {
                    columns_score.add(v.getCol());
                }
            }
        });
        ProjectDto project = surveyGrpcConvertMapper.toProjectDto(qdesGrpcClient.getProject(project_id));
        List<QstructsDto> qstruct_list = getQstructList(project_id, project.getReleaseVer());
        if (CollUtil.isEmpty(qstruct_list)) {
            return new_items;
        }
        //获取最新的外部参数
        SurveyExtParameter extparameters = getExtparameterByProjectId(project.getId(), null);
        Map<String, Integer> extparameters_type_map = new HashMap<>();
        if (Objects.nonNull(extparameters) && StrUtil.isNotEmpty(extparameters.getData())) {
            //把老的外部参数类型做一下转换
            JSONObject.parseArray(extparameters.getData(), JSONObject.class).forEach(data -> extparameters_type_map.put(data.getString("name"), extParamsTypeFormat(data.get("dtype"))));
        }
        //外部参数改版，如果新的外部参数和已经存在的外部参数同名类型不同，以新的外部参数类型为准
        JSONObject query = new JSONObject();
        query.put("survey_id", project.getId());
        List<DeliverExtParamsDto> deliver_ext_param_list = deliverGrpcClient.listDeliverExtparams(query);
        deliver_ext_param_list.forEach(deliver_ext_param -> extparameters_type_map.put(deliver_ext_param.getExtparamName(), deliver_ext_param.getExtparamType()));

        project.setQstruct(JSONObject.toJSONString(qstruct_list.get(0)));
        //获取当前用户是否有查看联系方式权限
        Map<Integer, Boolean> mdisplay_range_perm = new HashMap();
        if (StrUtil.isNotEmpty(userId)) {
            MEMBER_MTYPE_DICT.keySet().forEach(mtype -> mdisplay_range_perm.put(mtype, memberGrpcClient.checkMemberDisplayRangeByMtype(project.getCompanyId(), userId, mtype)));
        }
        for (SurveyRespondent surveyRespondent : items) {
            List<Map<String, Object>> sub_items = processItem(surveyRespondent, project, levels_front, columns_front, columns_behind, columns_question, sensitive_map, qstruct_list, columns_score, columns_member, mdisplay_range_perm, columns_ext_params, extparameters_type_map, enum_ext_params);
            new_items.add(sub_items);
        }
        return new_items;
    }

    private List<QstructsDto> getQstructList(String project_id, Integer releaseVer) {
        //做缓存
        return qdesGrpcClient.qdesListQstructWithq(project_id, releaseVer).stream().map(grpcConvertMapper::toQstructsDto).collect(Collectors.toList());
    }

    /**
     * 答卷数据处理
     *
     * @param surveyRespondent
     * @param project
     * @param levels_front
     * @param columns_front
     * @param columns_behind
     * @param columns_question
     * @param sensitive_map
     * @param qstruct_list
     * @param columns_score
     * @param columns_member
     * @param mdisplay_range_perm
     * @param columns_ext_params
     * @param extparameters_type_map
     * @param enum_ext_params
     * @return
     */
    private List<Map<String, Object>> processItem(SurveyRespondent surveyRespondent, ProjectDto project, List<SurveyColumn> levels_front, List<String> columns_front, List<String> columns_behind, List<SurveyColumn> columns_question, JSONObject sensitive_map, List<QstructsDto> qstruct_list, List<String> columns_score, List<String> columns_member, Map mdisplay_range_perm, List<String> columns_ext_params, Map<String, Integer> extparameters_type_map, JSONObject enum_ext_params) {
        List<Map<String, Object>> sub_items = new ArrayList<>();
        LambdaQueryWrapper<SurveyRespondentWeixin> surveyQueryWrapper = new LambdaQueryWrapper<>();
        surveyQueryWrapper.eq(SurveyRespondentWeixin::getRid, surveyRespondent.getId());
        SurveyRespondentWeixin wx_info = surveyRespondentWeixinMapper.selectOne(surveyQueryWrapper);
        surveyRespondent.setWxInfo(wx_info);

        LambdaQueryWrapper<SurveyRespondentData> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SurveyRespondentData::getRid, surveyRespondent.getId());
        wrapper.eq(SurveyRespondentData::getProjectId, project.getId());
        SurveyRespondentData rspd_data = surveyRespondentDataMapper.selectOne(wrapper);
        if (Objects.isNull(rspd_data)) {
            //respondentdata中没有值，给个默认的值。如果是 continue 答卷数据中会少一条数据
            rspd_data = new SurveyRespondentData();
            rspd_data.setAnswers(new JSONObject().toJSONString());
            rspd_data.setCustomAnswers(new JSONObject().toJSONString());
        }
        for (Integer tmp : COLUMNS_META_SORT) {
            if (tmp == COLUMN_CTYPE_META_FRONT.getNumber()) {
                sub_items = genMetaFront(sub_items, surveyRespondent, levels_front, columns_front);
            } else if (tmp == COLUMN_CTYPE_QUESTION.getNumber()) {
                try {
                    sub_items = genQuestionInfo(sub_items, surveyRespondent, columns_question, project, sensitive_map, rspd_data, qstruct_list);
                } catch (Exception e) {
                    log.error(String.format("答卷数据解析报错 seq: %s", surveyRespondent.getSeq()), e);
                    return Collections.emptyList();
                }
            } else if (tmp == COLUMN_CTYPE_META_BEHIND.getNumber()) {
                sub_items = genMetaBehind(sub_items, surveyRespondent, project, columns_behind);
            } else if (tmp == COLUMN_CTYPE_SCORE.getNumber()) {
                //总分字段只有一个字段
                sub_items = genScoreByCalc(sub_items, rspd_data, qstruct_list, columns_score);
            } else if (tmp == COLUMN_CTYPE_MEMBER.getNumber()) {
                sub_items = genMember(sub_items, surveyRespondent, columns_member, mdisplay_range_perm);
            } else if (tmp == COLUMN_CTYPE_EXTERNAL.getNumber()) {
                sub_items = genExtExparams(sub_items, rspd_data, columns_ext_params, extparameters_type_map, enum_ext_params);
            }
        }
        return sub_items;
    }

    @Override
    public List<Map<String, Object>> genQuestionInfo(List<Map<String, Object>> sub_items, SurveyRespondent surveyRespondent, List<SurveyColumn> columns_question, ProjectDto project, JSONObject sensitive_map, SurveyRespondentData rspd_data, List<QstructsDto> qstruct_list) {
        if (Objects.isNull(rspd_data)) {
            rspd_data = surveyColumnService.getRespondentDataByRid(surveyRespondent.getId(), project.getId());
        }
//        JSONArray answers = (JSONArray) parseAnswers(surveyRespondent.getProjectId(), project.getReleaseVer(), rspd_data.getAnswers(), false, surveyRespondent.getSeq(), false, null, project);
        JSONObject param = new JSONObject();
        param.put("id", surveyRespondent.getId());
        param.put("projectId", surveyRespondent.getProjectId());
        param.put("version", project.getReleaseVer());
        param.put("seq", surveyRespondent.getSeq());
        param.put("status", surveyRespondent.getStatus());
        param.put("answers", rspd_data.getAnswers());
        param.put("whole_qinfo", false);
        param.put("with_attach", false);
        param.put("sensitive_map", surveyColumnService.getUidsSensitives(surveyRespondent.getProjectId(), SecurityContextHolder.getUserId()));
        JSONArray answers = JSONArray.parseArray(surveyColumnService.parseAnswers(param, null).toString());

        Map<String, JSONObject> answer_dict = answers.stream().collect(Collectors.toMap(v -> ((JSONObject) v).getString("gid"), v -> (JSONObject) v, (a, b) -> a));
        if (CollUtil.isEmpty(qstruct_list)) {
            qstruct_list = qdesGrpcClient.qdesListQstructWithq(surveyRespondent.getProjectId(), surveyRespondent.getVersion()).stream().map(grpcConvertMapper::toQstructsDto).collect(Collectors.toList());
        }
        JSONObject scores = CollUtil.isNotEmpty(qstruct_list) && qstruct_list.size() == 1 ? JSONObject.parseObject(qstruct_list.get(0).getScores()) : new JSONObject();
        JSONObject score_config = qstruct_list.size() == 1 && StrUtil.isNotEmpty(qstruct_list.get(0).getScoreConfig()) ? JSONObject.parseObject(qstruct_list.get(0).getScoreConfig()) : new JSONObject();
        JSONObject score_dict = surveyColumnService.getScoreGidDict(scores, qstruct_list.get(0));

        List<Integer> gid_list = columns_question.stream().map(SurveyColumn::getGid).collect(Collectors.toList());

        LambdaQueryWrapper<SurveyRespondentModifyLog> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SurveyRespondentModifyLog::getProjectId, surveyRespondent.getProjectId());
        queryWrapper.eq(SurveyRespondentModifyLog::getSeq, surveyRespondent.getSeq());
        queryWrapper.in(SurveyRespondentModifyLog::getGid, gid_list);
        queryWrapper.orderByDesc(SurveyRespondentModifyLog::getUpdatedDt);
        List<SurveyRespondentModifyLog> modifylog_list = surveyRespondentModifyLogMapper.selectList(queryWrapper);
        List<Integer> reset_ans_flag_list = resetAnsList(surveyRespondent.getSeq(), gid_list);
        Map<Integer, SurveyRespondentModifyLog> modifylog_gid_data = new HashMap();
        modifylog_list.forEach(modify -> modifylog_gid_data.putIfAbsent(modify.getGid(), modify));

        //题目的第一列显示答案修改时间
        Set<Integer> gidset = new HashSet<>();
        Map<String, Object> ans_obj;
        for (SurveyColumn column : columns_question) {
            Integer gid = column.getGid();
            if (surveyColumnService.checkQuestionSensitive(sensitive_map, String.valueOf(gid))) {
                ans_obj = toRowsObj("****");
                ans_obj.put("sensitive", true);
            } else {
                JSONObject qanswer = answer_dict.get(String.valueOf(gid));
                ans_obj = toRowsObj("");
                if (CollUtil.isNotEmpty(qanswer)) {
                    Object ans = parse(column, qanswer, score_dict, score_config);
                    if ("image_select".equals(qanswer.getString("custom_qtype"))) {
                        ans_obj.put("answer", ListUtil.of(ans));
                    } else {
                        ans_obj = toRowsObj(ans);
                    }
                    ans_obj.put("custom_qtype", qanswer.getOrDefault("custom_qtype", ""));
                }
                if (!gidset.contains(column.getGid())) {
                    if (!reset_ans_flag_list.contains(gid)) {
                        SurveyRespondentModifyLog modifylog = modifylog_gid_data.get(gid);
                        gidset.add(gid);
                        if (Objects.nonNull(modifylog)) {
                            ans_obj.put("modify_date", modifylog.getUpdatedDt().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                        }
                    }
                }
            }
            sub_items.add(ans_obj);
        }
        return sub_items;
    }

    private Object parse(SurveyColumn column, JSONObject qanswer, JSONObject score_dict, JSONObject score_config) {
        //主函数，通过这个函数调用，方法下划线转驼峰
        String func = StrUtil.toCamelCase(String.format("column_parse_%s", qanswer.getString("qtype")));
        Method method;
        try {
            method = this.getClass().getDeclaredMethod(func, SurveyColumn.class, JSONObject.class, JSONObject.class, JSONObject.class);
            method.setAccessible(true);
            return method.invoke(this, column, qanswer, score_dict, score_config);
        } catch (Exception e) {
            log.error("函数{}调用异常：", func, e);
            throw new BusinessException("函数调用异常：" + e.getMessage());
        }
    }

    public Object columnParseSingle(SurveyColumn column, JSONObject qanswer, JSONObject score_dict, JSONObject score_config) {
        Integer qtype = column.getQtype();
        List<Object> answer = (List<Object>) qanswer.get("answer");
        if (CollUtil.isNotEmpty(answer) && answer.get(0) instanceof JSONObject) {
            JSONObject obj = (JSONObject) answer.get(0);
            if (qtype == COLUMN_QTYPE_QUESTION.getNumber()) {
                if ("image_select".equals(qanswer.getString("custom_qtype"))) {
                    return Arrays.asList(obj.getString("title"), obj.getString("src"));
                }
                return obj.getString("title");
            } else if (qtype == COLUMN_QTYPE_OPEN.getNumber()) {
                if (ObjectUtil.equals(column.getOgid(), obj.getInteger("gid"))) {
                    return obj.getOrDefault("open", "");
                }
                return "";
            } else if (qtype == COLUMN_QTYPE_SCORE.getNumber()) {
                if (CollUtil.isNotEmpty(score_dict)) {
                    Float tmp_score = score_dict.getFloat(String.format("%s_%s", column.getGid(), obj.getInteger("gid")));
                    if (Objects.nonNull(tmp_score)) {
                        return Float.valueOf(Float.parseFloat(String.format("%.2f", tmp_score)));
                    }
                    return 0.00f;
                }
            }
        }
        return "";
    }

    public Object columnParseMultiple(SurveyColumn column, JSONObject qanswer, JSONObject score_dict, JSONObject score_config) {
        Integer qtype = column.getQtype();
        List<Object> answer = (List<Object>) qanswer.get("answer");
        if (qtype == COLUMN_QTYPE_QUESTION.getNumber()) {
            if (ObjectUtil.isNotEmpty(answer)) {
                List tmp_ans;
                //多选题答案， 如果第一个选项被选择，再把题目选项中的第一个选项删除了，导致其他选项的答案也显示不出来
                if ("image_select".equals(qanswer.getString("custom_qtype"))) {
                    tmp_ans = new ArrayList();
                    for (Object a : answer) {
                        if (a instanceof JSONObject) {
                            tmp_ans.addAll(Arrays.asList(((JSONObject) a).getString("title"), ((JSONObject) a).getString("src")));
                        }
                    }
                    return tmp_ans;
                } else {
                    tmp_ans = new ArrayList();
                    for (Object a : answer) {
                        if (a instanceof JSONObject) {
                            tmp_ans.addAll(Arrays.asList(((JSONObject) a).getString("title")));
                        }
                    }

                    return StrUtil.join("<br>", tmp_ans);
                }
            }

        } else if (qtype == COLUMN_QTYPE_OPEN.getNumber()) {
            if (CollUtil.isNotEmpty(answer) && answer.get(0) instanceof JSONObject) {
                for (Object obj : answer) {
                    if (obj instanceof JSONObject && ObjectUtil.equals(column.getOgid(), ((JSONObject) obj).getInteger("gid"))) {
                        return ((JSONObject) obj).getOrDefault("open", "");
                    }
                }
            }
        } else if (qtype == COLUMN_QTYPE_SCORE.getNumber()) {
            List<Integer> answer_ids = answer.stream().map(v -> ((JSONObject) v).getInteger("gid")).collect(Collectors.toList());
            String qgid = qanswer.getString("gid");
            AtomicReference<Float> score = new AtomicReference<>(0.0f);
            if ("image_select".equals(qanswer.getString("custom_qtype")) || !SCORE_RULE_ALL_RIGHT.equals(score_config.get(qgid)) || BaseTypeHandler.checkAnswerAllRight(qgid, score_dict, answer_ids, qanswer.getString("qtype"))) {
                answer.stream().filter(v -> v instanceof JSONObject).forEach(v -> {
                    Float value = convertFloat((String) score_dict.getOrDefault(String.format("%s_%s", column.getGid(), ((JSONObject) v).getString("gid")), ""));
                    if (Objects.nonNull(value)) {
                        score.updateAndGet(v1 -> v1 + value);
                    }
                });
            }
            return Float.parseFloat(String.format("%.2f", score.get()));
        }
        return "";
    }

    /**
     * 矩阵单选题
     *
     * @param column
     * @param qanswer
     * @param score_dict
     * @param score_config
     * @return
     */
    public Object columnParseMatrixSingle(SurveyColumn column, JSONObject qanswer, JSONObject score_dict, JSONObject score_config) {
        List<JSONObject> answer = (List<JSONObject>) qanswer.get("answer");
        if (CollUtil.isEmpty(answer)) {
            return "";
        }
        Integer qtype = column.getQtype();
        if (qtype == COLUMN_QTYPE_QUESTION.getNumber()) {
            for (JSONObject row : answer) {
                JSONArray rowAnswer;
                if (ObjectUtil.equals(column.getRgid(), row.getInteger("gid")) && CollUtil.isNotEmpty(rowAnswer = row.getJSONArray("answer"))) {
                    return ((JSONObject) rowAnswer.get(0)).getString("title");
                }
            }
        } else if (qtype == COLUMN_QTYPE_OPEN.getNumber()) {
            for (JSONObject row : answer) {
                if (ObjectUtil.equals(column.getRgid(), row.getInteger("gid")) && (Objects.isNull(column.getOgid()) || !column.getOgid().equals(0))) {
                    return row.getOrDefault("open", "");
                } else {
                    JSONArray rowAnswer;
                    if (ObjectUtil.equals(column.getRgid(), row.getInteger("gid")) && CollUtil.isNotEmpty(rowAnswer = row.getJSONArray("answer")) && ObjectUtil.equals(column.getOgid(), ((JSONObject) rowAnswer.get(0)).getString("gid"))) {
                        return row.getOrDefault("open", "");
                    }
                }
            }
        } else if (qtype == COLUMN_QTYPE_SCORE.getNumber()) {
            if (CollUtil.isNotEmpty(score_dict)) {
                AtomicReference<Float> score = new AtomicReference<>(0.0f);
                answer.stream().filter(row -> CollUtil.isNotEmpty(row)).forEach(row -> {
                    Float value = convertFloat((String) score_dict.getOrDefault(String.format("%s_%s_%s", column.getGid(), row.getString("gid"), ((JSONObject) row.getJSONArray("answer").get(0)).get("gid")), ""));
                    if (Objects.nonNull(value)) {
                        score.updateAndGet(v1 -> v1 + value);
                    }
                });
                return Float.parseFloat(String.format("%.2f", score.get()));
            }
        }
        return "";
    }


    /**
     * 矩阵多选题
     *
     * @param column
     * @param qanswer
     * @param score_dict
     * @param score_config
     * @return
     */
    public Object columnParseMatrixMultiple(SurveyColumn column, JSONObject qanswer, JSONObject score_dict, JSONObject score_config) {
        List<JSONObject> answer = (List<JSONObject>) qanswer.get("answer");
        if (CollUtil.isEmpty(answer)) {
            return "";
        }
        Integer qtype = column.getQtype();
        if (qtype == COLUMN_QTYPE_QUESTION.getNumber()) {
            for (JSONObject row : answer) {
                JSONArray rowAnswer;
                if (ObjectUtil.equals(column.getRgid(), row.getInteger("gid")) && CollUtil.isNotEmpty(rowAnswer = row.getJSONArray("answer"))) {
                    return StrUtil.join("<br>", rowAnswer.stream().map(v -> ((JSONObject) v).getString("title")).collect(Collectors.toList()));
                }
            }
        } else if (qtype == COLUMN_QTYPE_OPEN.getNumber()) {
            for (JSONObject row : answer) {
                if (ObjectUtil.equals(column.getRgid(), row.getInteger("gid")) && (Objects.isNull(column.getOgid()) || !column.getOgid().equals(0))) {
                    return row.getOrDefault("open", "");
                } else {
                    List<JSONObject> rowAnswer;
                    if (ObjectUtil.equals(column.getRgid(), row.getInteger("gid")) && CollUtil.isNotEmpty(rowAnswer = (List<JSONObject>) row.get("answer"))) {
                        for (JSONObject rans : rowAnswer) {
                            if (ObjectUtil.equals(column.getOgid(), rans.getInteger("gid"))) {
                                return row.getOrDefault("open", "");
                            }
                        }
                    }
                }
            }
        } else if (qtype == COLUMN_QTYPE_SCORE.getNumber()) {
            if (CollUtil.isNotEmpty(score_dict)) {
                String qgid = qanswer.getString("gid");
                AtomicReference<Float> score = new AtomicReference<>(0.0f);
                answer.forEach(row -> {
                    //整理每行的 答案id 和 设置的得分 id
                    List<JSONObject> row_answers = (List<JSONObject>) row.get("answer");
                    if (CollUtil.isNotEmpty(row_answers)) {
                        //{'9': [[7]], '10': [[8]], '11': [[7], [8]]}
                        Map<String, List> row_answer_ids = new HashMap();
                        row_answers.forEach(col -> {
                            List list;
                            if (Objects.isNull(list = row_answer_ids.get(row.getString("gid")))) {
                                list = new ArrayList();
                            }
                            list.add(col.getString("gid"));
                            row_answer_ids.put(row.getString("gid"), list);
                        });
                        JSONObject row_score_dict = new JSONObject();
                        score_dict.forEach((i, v) -> {
                            if (i.startsWith(String.format("%s_%s", qgid, row.getString("gid")))) {
                                row_score_dict.put(i, v);
                            }
                        });

                        //非全对得分 或 全对得分时，答案全对
                        if (!SCORE_RULE_ALL_RIGHT.equals(score_config.get(qgid)) || !BaseTypeHandler.checkAnswerAllRight(qgid, row_score_dict, row_answer_ids, qtype.toString())) {
                            row_answers.forEach(opt -> {
                                Float value = convertFloat((String) score_dict.getOrDefault(String.format("%s_%s_%s", column.getGid(), row.getString("gid"), opt.get("gid")), ""));
                                if (Objects.nonNull(value)) {
                                    score.updateAndGet(v1 -> v1 + value);
                                }
                            });
                        }
                    }
                });
                return Float.parseFloat(String.format("%.2f", score.get()));
            }
        }
        return "";
    }


    public Object columnParseCascader(SurveyColumn column, JSONObject qanswer, JSONObject score_dict, JSONObject score_config) {
        List<JSONObject> answer = (List<JSONObject>) qanswer.getOrDefault("answer", Collections.emptyList());
        Integer qtype = column.getQtype();
        if (qtype == COLUMN_QTYPE_C1.getNumber() && answer.size() > 0) {
            return answer.get(0).getOrDefault("title", "");
        } else if (qtype == COLUMN_QTYPE_C2.getNumber() && answer.size() > 1) {
            return answer.get(1).getOrDefault("title", "");
        } else if (qtype == COLUMN_QTYPE_C3.getNumber() && answer.size() > 2) {
            return answer.get(2).getOrDefault("title", "");
        } else if (qtype == COLUMN_QTYPE_C4.getNumber() && answer.size() > 3) {
            return answer.get(3).getOrDefault("title", "");
        }
        return "";
    }

    public Object columnParseBlank(SurveyColumn column, JSONObject qanswer, JSONObject score_dict, JSONObject score_config) {
        Integer qtype = column.getQtype();
        if (qtype == COLUMN_QTYPE_QUESTION.getNumber()) {
            List<JSONObject> answer = (List<JSONObject>) qanswer.getOrDefault("answer", Collections.emptyList());
            return StrUtil.join("<br>", answer.stream().map(v -> v.getString("blank")).collect(Collectors.toList()));
        }
        return "";
    }

    public Object columnParseMultipleBlank(SurveyColumn column, JSONObject qanswer, JSONObject score_dict, JSONObject score_config) {
        Integer qtype = column.getQtype();
        if (qtype == COLUMN_QTYPE_QUESTION.getNumber()) {
            List<JSONObject> answer = (List<JSONObject>) qanswer.getOrDefault("answer", Collections.emptyList());
            for (JSONObject v : answer) {
                if (ObjectUtil.equals(column.getOgid(), v.getInteger("gid"))) {
                    return v.getString("blank");
                }
            }
        }
        return "";
    }

    public Object columnParseAutoTable(SurveyColumn column, JSONObject qanswer, JSONObject score_dict, JSONObject score_config) {
        Integer qtype = column.getQtype();
        if (qtype == COLUMN_QTYPE_QUESTION.getNumber()) {
            List<JSONObject> answer = (List<JSONObject>) qanswer.getOrDefault("answer", Collections.emptyList());
            if (CollUtil.isNotEmpty(answer)) {
                return String.format("%s行", ((List) answer.get(0).getOrDefault("answer", Collections.emptyList())).size());
            }
        }
        return "";
    }

    /**
     * 打分排序-评价题
     *
     * @param column
     * @param qanswer
     * @param score_dict
     * @param score_config
     * @return
     */
    public Object columnParseEvaluation(SurveyColumn column, JSONObject qanswer, JSONObject score_dict, JSONObject score_config) {
        JSONObject answer = (JSONObject) qanswer.get("answer");
        if (CollUtil.isEmpty(answer)) {
            return "";
        }
        Integer qtype = column.getQtype();
        if (qtype == COLUMN_QTYPE_QUESTION.getNumber()) {
            return answer.getString("score");
        } else if (qtype == COLUMN_QTYPE_TAG.getNumber()) {
            List<Integer> tags_id;
            if (CollUtil.isNotEmpty(tags_id = (List) answer.get("tagsId"))) {
                Map<Integer, Map<Integer, Object>> tag_list = (Map) ((Map) qanswer.getOrDefault("current_template", Collections.emptyMap())).getOrDefault("tagList", Collections.emptyMap());
                StringBuilder res = new StringBuilder();
                for (Integer tag_id : tags_id) {
                    String title;
                    if (Objects.nonNull(tag_id) && StrUtil.isNotEmpty(title = (String) Optional.ofNullable(tag_list.get(tag_id)).orElse(Collections.emptyMap()).get("title"))) {
                        res.append(title + "<br>");
                    }
                }
                return res.length() > 0 ? res.substring(0, res.lastIndexOf("<br>")) : "";
            } else if (CollUtil.isNotEmpty(tags_id = (List) answer.get("tags"))) {
                return StrUtil.join("<br>", tags_id);
            }
            return "";
        } else if (qtype == COLUMN_QTYPE_OPEN.getNumber()) {
            return answer.getOrDefault("open", "");
        }
        return "";
    }


    public Object columnParseScore(SurveyColumn column, JSONObject qanswer, JSONObject score_dict, JSONObject score_config) {
        Integer qtype = column.getQtype();
        List<JSONObject> answer = (List<JSONObject>) qanswer.getOrDefault("answer", Collections.emptyList());
        List<String> tmp_ans = answer.stream().map(v -> Objects.nonNull(v.getString("score")) ? v.getString("score") : " ").collect(Collectors.toList());
        if (qtype == COLUMN_QTYPE_QUESTION.getNumber()) {
            return StrUtil.join("<br>", tmp_ans);
        }
        //nps客户类型显示
        if (ObjectUtil.equal(COLUMN_QTYPE_NPS_CUSTOMER.getNumber(), qtype)) {
            String tmp = StrUtil.join("<br>", tmp_ans);
            if (Arrays.asList("7", "8").contains(tmp)) {
                tmp = "中立者 (7-8分)";
            } else if (Arrays.asList("0", "1", "2", "3", "4", "5", "6").contains(tmp)) {
                tmp = "贬低者 (0-6分）";
            } else if (Arrays.asList("7", "8").contains(tmp)) {
                tmp = "推荐者 (9-10分）";
            }
            return tmp;
        }
        return "";
    }

    public Object columnParseSort(SurveyColumn column, JSONObject qanswer, JSONObject score_dict, JSONObject score_config) {
        Integer qtype = column.getQtype();
        List<JSONObject> answer = (List<JSONObject>) qanswer.getOrDefault("answer", Collections.emptyList());
        if (qtype == COLUMN_QTYPE_QUESTION.getNumber()) {
            for (JSONObject v : answer) {
                if (ObjectUtil.equals(column.getOgid(), v.getInteger("gid"))) {
                    Object sort = v.get("sort");
                    if (sort instanceof List) {
                        return ((List) sort).get(0);
                    } else {
                        return sort;
                    }
                }
            }
        } else if (qtype == COLUMN_QTYPE_OPEN.getNumber()) {
            for (JSONObject v : answer) {
                Object sort;
                if (ObjectUtil.equals(column.getOgid(), v.getInteger("gid")) && (sort = v.get("sort")) instanceof List) {
                    return ((List) sort).get(1);
                }
            }
        }
        return "";
    }


    public Object columnParseProportion(SurveyColumn column, JSONObject qanswer, JSONObject score_dict, JSONObject score_config) {
        Integer qtype = column.getQtype();
        if (qtype == COLUMN_QTYPE_QUESTION.getNumber()) {
            List<JSONObject> answer = (List<JSONObject>) qanswer.getOrDefault("answer", Collections.emptyList());
            for (JSONObject v : answer) {
                if (ObjectUtil.equals(column.getOgid(), v.getInteger("gid"))) {
                    return v.getOrDefault("score", "");
                }
            }
        }
        return "";
    }


    /**
     * 矩阵填空题
     *
     * @param column
     * @param qanswer
     * @param score_dict
     * @param score_config
     * @return
     */
    public Object columnParseMatrixBlank(SurveyColumn column, JSONObject qanswer, JSONObject score_dict, JSONObject score_config) {
        Integer qtype = column.getQtype();
        List<JSONObject> answer = (List<JSONObject>) qanswer.getOrDefault("answer", Collections.emptyList());
        if (qtype == COLUMN_QTYPE_QUESTION.getNumber()) {
            for (JSONObject row : answer) {
                if (ObjectUtil.equals(column.getRgid(), row.getInteger("gid"))) {
                    List<JSONObject> row_answers = (List<JSONObject>) row.get("answer");
                    for (JSONObject opt : row_answers) {
                        if (ObjectUtil.equals(column.getOgid(), opt.getInteger("gid"))) {
                            return opt.getString("blank");
                        }
                    }
                }
            }
        } else if (qtype == COLUMN_QTYPE_OPEN.getNumber()) {
            for (JSONObject row : answer) {
                if (ObjectUtil.equals(column.getRgid(), row.getInteger("gid")) && (Objects.isNull(column.getOgid()) || !column.getOgid().equals(0))) {
                    return row.getOrDefault("open", "");
                }
            }
        }
        return "";
    }

    public Object columnParseMatrixScore(SurveyColumn column, JSONObject qanswer, JSONObject score_dict, JSONObject score_config) {
        Integer qtype = column.getQtype();
        List<JSONObject> answer = (List<JSONObject>) qanswer.getOrDefault("answer", Collections.emptyList());
        if (qtype == COLUMN_QTYPE_QUESTION.getNumber()) {
            for (JSONObject row : answer) {
                if (ObjectUtil.equals(column.getRgid(), row.getInteger("gid"))) {
                    List<JSONObject> row_answers = (List<JSONObject>) row.get("answer");
                    for (JSONObject opt : row_answers) {
                        if (ObjectUtil.equals(column.getOgid(), opt.getInteger("gid"))) {
                            return opt.getString("score");
                        }
                    }
                }
            }
        } else if (qtype == COLUMN_QTYPE_OPEN.getNumber()) {
            for (JSONObject row : answer) {
                if (ObjectUtil.equals(column.getRgid(), row.getInteger("gid")) && (Objects.isNull(column.getOgid()) || !column.getOgid().equals(0))) {
                    return row.getOrDefault("open", "");
                }
            }
        }
        return "";
    }


    public Object columnParseTimedelta(SurveyColumn column, JSONObject qanswer, JSONObject score_dict, JSONObject score_config) {
        if ("".equals(qanswer.getOrDefault("answer", ""))) {
            return "";
        }
        if (column.getQtype() == COLUMN_QTYPE_QUESTION.getNumber()) {
            return qanswer.get("answer");
        }
        return "";
    }

    public Object columnParseTimestamp(SurveyColumn column, JSONObject qanswer, JSONObject score_dict, JSONObject score_config) {
        if (ObjectUtil.isEmpty(qanswer.get("answer"))) {
            return "";
        }
        if (column.getQtype() == COLUMN_QTYPE_QUESTION.getNumber()) {
            Map<String, Object> map = new HashMap<>();
            map.put("ts", qanswer.getJSONObject("answer").get("ts"));
            map.put("tz", qanswer.getJSONObject("answer").get("tz"));
            return map;
        }
        return "";
    }


    public Object columnParseGeo(SurveyColumn column, JSONObject qanswer, JSONObject score_dict, JSONObject score_config) {
        JSONObject answer = (JSONObject) qanswer.getOrDefault("answer", Collections.emptyList());
        if (ObjectUtil.isEmpty(qanswer.get("answer"))) {
            return "";
        }
        Integer qtype = column.getQtype();
        if (qtype == COLUMN_QTYPE_QUESTION.getNumber()) {
            return answer.getOrDefault("formattedAddress", "");
        } else if (qtype == COLUMN_QTYPE_PROVINCE.getNumber()) {
            return ((Map) answer.getOrDefault("city", Collections.emptyMap())).get("1");
        } else if (qtype == COLUMN_QTYPE_CITY.getNumber()) {
            return ((Map) answer.getOrDefault("city", Collections.emptyMap())).get("2");
        } else if (qtype == COLUMN_QTYPE_DISTRICT.getNumber()) {
            return ((Map) answer.getOrDefault("city", Collections.emptyMap())).get("3");
        } else if (qtype == COLUMN_QTYPE_ADDRESS.getNumber()) {
            return ((Map) answer.getOrDefault("city", Collections.emptyMap())).get("4");
        } else if (qtype == COLUMN_QTYPE_LONGITUDE.getNumber()) {
            List coordinate = (List) answer.get("coordinate");
            if (CollUtil.isNotEmpty(coordinate)) {
                return coordinate.get(0);
            }
        } else if (qtype == COLUMN_QTYPE_LATITUDE.getNumber()) {
            List coordinate = (List) answer.get("coordinate");
            if (CollUtil.isNotEmpty(coordinate)) {
                return coordinate.get(1);
            }
        } else if (qtype == COLUMN_QTYPE_COORDINATE.getNumber()) {
            List coordinate = (List) answer.get("coordinate");
            if (CollUtil.isNotEmpty(coordinate)) {
                return coordinate;
            }
        }
        return "";
    }

    public Object columnParseCity(SurveyColumn column, JSONObject qanswer, JSONObject score_dict, JSONObject score_config) {
        JSONArray answer = (JSONArray) qanswer.get("answer");
        if (ObjectUtil.isEmpty(answer)) {
            return "";
        }
        Integer qtype = column.getQtype();
        if (qtype == COLUMN_QTYPE_PROVINCE.getNumber()) {
            return answer.size() > 0 ? answer.get(0) : "";
        } else if (qtype == COLUMN_QTYPE_CITY.getNumber()) {
            return answer.size() > 1 ? answer.get(1) : "";
        } else if (qtype == COLUMN_QTYPE_DISTRICT.getNumber()) {
            return answer.size() > 2 ? answer.get(2) : "";
        } else if (qtype == COLUMN_QTYPE_ADDRESS.getNumber()) {
            return answer.size() > 3 ? answer.get(3) : "";
        }
        return "";
    }

    private List<Integer> resetAnsList(Long seq, List<Integer> gids) {
        Map<Integer, List<SurveyRespondentModifyLog>> modify_log_data = findRespondentModifyLogAll(seq, gids);
        List<Integer> result = new ArrayList();
        modify_log_data.forEach((gid, data) -> {
            if (CollUtil.isNotEmpty(data) && data.get(0).getOldAnswer().equals(data.get(data.size() - 1).getNewAnswer())) {
                result.add(gid);
            }
        });
        return result;
    }

    private Map<Integer, List<SurveyRespondentModifyLog>> findRespondentModifyLogAll(Long seq, List<Integer> gids) {
        //排序查询RespondentModifyLog表中答卷(seq)中对应题目(gid)的数据
        LambdaQueryWrapper<SurveyRespondentModifyLog> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SurveyRespondentModifyLog::getSeq, seq);
        queryWrapper.in(SurveyRespondentModifyLog::getGid, gids);
        queryWrapper.orderByAsc(SurveyRespondentModifyLog::getUpdatedDt);
        List<SurveyRespondentModifyLog> modify_log_old = surveyRespondentModifyLogMapper.selectList(queryWrapper);
        Map<Integer, List<SurveyRespondentModifyLog>> modify_log_data = new HashMap();
        modify_log_old.forEach(item -> {
            Integer gid = item.getGid();
            List<SurveyRespondentModifyLog> list = modify_log_data.get(gid);
            if (Objects.isNull(list)) {
                list = new ArrayList<>();
            }
            list.add(item);
            modify_log_data.put(gid, list);
        });
        return modify_log_data;
    }

//    /**
//     * @param projectId
//     * @param version
//     * @param answers
//     * @param whole_qinfo   导出单份答卷时设置为True
//     * @param seq           答卷SEQ
//     * @param with_attach   带附件信息
//     * @param sensitive_map
//     * @param project       带有 qstruct 的project
//     * @return [{'gid': 2, 'cid': 1, '_index': 1, 'title': '您向朋友或同事推荐我们的可能性有多大？', 'qtype': 'score', 'custom_qtype': 'nps', 'answer': [{'gid': 3, 'seq': 3, 'title': '选项1', 'score': 9}]}]
//     */
//    private Object parseAnswers(String projectId, Integer version, String answers, boolean whole_qinfo, Long seq, boolean with_attach, Map sensitive_map, ProjectDto project) {
//        if (Objects.isNull(project)) {
//            project = surveyGrpcConvertMapper.toProjectDto(qdesGrpcClient.getReleasedProject(projectId, version));
//            if (Objects.isNull(project)) {
//                Map res = new HashMap(2);
//                res.put("data", new HashMap<>());
//                return res;
//            }
//        }
//        //String custom_cid = JSONObject.parseObject(project.getCustomAttr()).getString("openCustomCid");
//        JSONObject qstruct = JSONObject.parseObject(project.getQstruct());
//        JSONObject data = qstruct.getJSONObject("data");
//        JSONArray items = data.getJSONArray("items");
//        qstruct.put("data", data);
//        JSONArray answer_list = new JSONArray();
//        data.put("answer_list", answer_list);
//        data.put("answers", JSON.parseObject(answers));
//        data.put("seq", seq);
//        data.put("with_attach", with_attach);
//        data.put("sensitive_map", sensitive_map);
//        surveyColumnService.parseAnswerItem(data, 1);
//        if (whole_qinfo) {
//            items = answer_list;
//            data.put("items", items);
//            qstruct.put("data", data);
//            return qstruct;
//        }
//        return data.getJSONArray("answer_list");
//    }

    private JSONObject getFilter(JSONObject params, ProjectDto project) {
        String project_id = project.getId();
        JSONObject object = new JSONObject();
        object.put("status", Arrays.asList(RESPONDENT_STATUS_FINISH.getValue()));
        object.put("source", RESPONDENT_FORMAL_SOURCE);
        String member_id = params.getString("member_id");
        String orgId;
        if (StrUtil.isNotEmpty(member_id)) {
            object.put("member_id", member_id);
        }
        if (StrUtil.isNotEmpty(project_id)) {
            object.put("project_id", project_id);
            orgId = project.getCompanyId();
        } else if (StrUtil.isNotEmpty(member_id)) {
            MemberDto member = memberGrpcClient.getMemberById(member_id);
            orgId = member.getOrgId();
        } else {
            throw new BusinessException("project_id or :member_id is required");
        }
        checkOrg(orgId, SecurityContextHolder.getOrgId());
        TokenDTO token = SecurityContextHolder.getToken();
        if (Objects.isNull(token.getSuperRole()) && Objects.isNull(token.getIsSenior())) {
            List group_ids = new ArrayList<>();
            GroupDto root = userGrpcClient.getRootGroupByOrgId(orgId);
            if (Objects.isNull(root)) {
                throw new BusinessException("Group root node not found");
            }
            List<UserGroupDto> user_group_list = userGrpcClient.listUserGroup(root.getId(), SecurityContextHolder.getUserId());
            user_group_list.forEach(v -> {
                GroupNode data = userGrpcClient.getGroupTreeByRootGroupId(v.getGroupId(), false);
                getGroupIds(data, group_ids);
            });
            //通用答卷所有用户可见
            group_ids.add("");
            params.put("group_id", group_ids);
        }
        String seq = params.getString("seq");
        if (StrUtil.isNotEmpty(seq)) {
            try {
                params.put("seq", Integer.parseInt(seq));
            } catch (Exception e) {
                throw new BusinessException(String.format("seq[%s] must be number", seq));
            }
        }
        Arrays.asList("source", "version", "status").forEach(field -> {
            Object value = params.remove(field);
            if (value instanceof Integer) {
                params.put(field, value);
            } else if (ObjectUtil.isNotEmpty(value)) {
                try {
                    params.put(field, Arrays.stream(((String) value).split(",")).map(Integer::parseInt).collect(Collectors.toList()));
                } catch (Exception e) {
                    throw new BusinessException(String.format("%s[%s] must be number", field, seq));
                }
            }
        });
        String group_id = (String) params.remove("groupID");
        Object qList = params.remove("qList");
        HttpServletRequest request;
        try {
            request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
        } catch (Exception e) {
            request = SurveyExecutorConfig.OauthContext.get();
        }
        if (ObjectUtil.isNotEmpty(qList) && cn.hutool.http.Method.GET.name().equals(request.getMethod())) {
            qList = JSONObject.parseObject((String) qList);
        }
        //conda_list 已经取消使用了,已经从i前端移除,这里直接赋值一个None
        List cond_list = null;
        if (StrUtil.isNotEmpty(group_id)) {
            List group_ids = (List) Arrays.stream(group_id.split(","));
            try {
                group_ids.set(group_ids.indexOf("general"), "");
            } catch (Exception e) {

            }
            params.put("group_ids", group_ids);
        }
        if (ObjectUtil.isNotEmpty(qList) || ObjectUtil.isNotEmpty(cond_list)) {
            //需要根据问题筛选，则通过prop属性筛选
            qList = Optional.ofNullable(qList).orElse(Collections.emptyList());
            cond_list = (List) Optional.ofNullable(qList).orElse(Collections.emptyList());
            String qlogic = (String) params.getOrDefault("qLogic", "and");
            String condlogic = (String) params.getOrDefault("condLogic", "and");
            //根据筛选条件筛选出符合条件的seq
            List<Long> seq_list = getSeqByProp(params, (List<Map>) qList, cond_list, qlogic, condlogic);
            /*if (CollUtil.isEmpty(seq_list)) {
                //筛选后的seq为空的话则直接返回为null
                return null;
            }*/
            params.put("seq", seq_list);
            params.put("project_id", project_id);
        }
        //外部参数候选人
        Map ext_p = (Map) params.remove("ext_p");
        if (CollUtil.isNotEmpty(ext_p)) {
            List<SurveyRespondentPropData> seq_list_p = getSeqByPropExtP(params, ext_p);
            params.put("seq", params.containsKey("seq") ? new ArrayList(CollUtil.intersection(new HashSet<>((List) params.getJSONObject("seq").get("in")), new HashSet<>(seq_list_p))) : seq_list_p);
        }
        //早期的外部参数筛选,联系人相关，下面的代码应该不用了
        /*Object ext = params.remove("ext");
        if (ObjectUtil.isNotEmpty(ext) && cn.hutool.http.Method.GET.name().equals(request.getMethod())) {
            ext = JSONObject.parseObject((String) ext);
        }
        if (ObjectUtil.isNotEmpty(ext)) {
            List<SurveyRespondentProp> seq_list2 = getSeqByPropExt(params, (JSONObject) ext);
            Map<String, List> data = new HashMap<>();
            data.put("$in", params.containsKey("seq") ? new ArrayList(CollUtil.intersection(new HashSet<>((List) params.getJSONObject("seq").get("in")), new HashSet<>(seq_list2))) : seq_list2);
            params.put("seq", data);
        }*/
        params.forEach((key, val) -> {
            if (val instanceof List) {
                if (((List) val).size() == 1) {
                    params.put(key, ((List) val).get(0));
                } else {
                    params.put(key, val);
                }
            }
        });
        Map<String, LocalDateTime> timeMap = new HashMap(4);
        Arrays.asList("created_dt_gte", "created_dt_lt").forEach(field -> {
            String value = (String) params.remove(field);
            if (ObjectUtil.isNotEmpty(value)) {
                String[] fieldValue = field.split("_");
                try {
                    timeMap.put(fieldValue[fieldValue.length - 1], LocalDateTimeUtil.parse(value, DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
                } catch (Exception e) {
                    throw new BusinessException(String.format("%s[%s] must be yyyyMMddHHmmss", field, value));
                }
            }
        });
        if (!timeMap.isEmpty()) {
            params.put("created_dt", timeMap);
        }
        //答题开始时间
        formatDatetime(params, "begin_time", "rspd_st_gte", "rspd_st_lt");
        //答题结束时间
        formatDatetime(params, "finish_time", "rspd_ed_gte", "rspd_ed_lt");

        //问卷推送时间
        String deliver_dt_gte = params.getString("deliver_dt_gte");
        String deliver_dt_lt = params.getString("deliver_dt_lt");
        List deliver_ids = new ArrayList();
        if (StrUtil.isNotEmpty(project_id)) {
            if (StrUtil.isNotEmpty(deliver_dt_gte) || StrUtil.isNotEmpty(deliver_dt_lt)) {
                //获取当前问卷 推送时间段的所有的deliver_ids
                deliver_ids = getDeliverIdsByDeliverTime(deliver_dt_gte, deliver_dt_lt, project_id);
            }
        }
        String deliver_id = (String) params.remove("deliver_id");
        log.info(String.format("获取到的参数===%s", deliver_id));
        List deliver_id_list = new ArrayList();
        if (StrUtil.isNotEmpty(deliver_id)) {
            deliver_id_list = Arrays.asList(deliver_id.split(","));
        }
        if ((StrUtil.isNotEmpty(deliver_dt_gte) || StrUtil.isNotEmpty(deliver_dt_lt)) && StrUtil.isNotEmpty(deliver_id)) {
            //取交集
            params.put("deliver_id", new ArrayList(CollUtil.intersection(new HashSet<>(deliver_ids), new HashSet<>(deliver_id_list))));
        } else if ((StrUtil.isNotEmpty(deliver_dt_gte) || StrUtil.isNotEmpty(deliver_dt_lt)) && StrUtil.isEmpty(deliver_id)) {
            params.put("deliver_id", deliver_ids);
        } else if (!((StrUtil.isNotEmpty(deliver_dt_gte) || StrUtil.isNotEmpty(deliver_dt_lt)) && StrUtil.isNotEmpty(deliver_id))) {
            params.put("deliver_id", deliver_id_list);
        }
        return params;
    }

    /**
     * 根据具体投放时间的上下限，以及project_id来查询出deliver_id列表
     *
     * @param deliver_dt_gte 投放时间的下限，要获取在此时间之上的deliver
     * @param deliver_dt_lt  投放时间的上限，要获取在此时间之下的deliver
     * @param project_id     要获取本项目的投放
     * @return deliver_id_list是根据project_id，以及deliver_dt_gte，deliver_dt_lte过滤出来的deliver_id的列表，每个元素是字符串
     */
    private List<String> getDeliverIdsByDeliverTime(String deliver_dt_gte, String deliver_dt_lt, String project_id) {
        return deliverGrpcClient.getDeliverIdsByDtPid(deliver_dt_gte, deliver_dt_lt, project_id);
    }

    /**
     * 处理时间
     *
     * @param params
     * @param params_time 对某个时间字段进行筛选
     * @param start_time  时间字段的开始时间
     * @param end_time    时间字段的结束时间
     */
    private void formatDatetime(JSONObject params, String params_time, String start_time, String end_time) {
        Map<String, LocalDateTime> timeMap = new HashMap(4);
        try {
            String value = (String) params.remove(start_time);
            if (Objects.nonNull(value)) {
                //将时分秒置为0
                timeMap.put("$gte", LocalDateTimeUtil.parse(value.substring(0, value.length() - 6), DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));
            }
            value = (String) params.remove(end_time);
            if (Objects.nonNull(value)) {
                //将时分秒置为0
                timeMap.put("$lt", LocalDateTimeUtil.parse(value.substring(0, value.length() - 6), DateTimeFormatter.ofPattern("yyyyMMddHHmmss")).plusDays(1));
            }
        } catch (Exception e) {
            throw new BusinessException(String.format("%s must be yyyyMMddHHmmss", params_time));
        }
        if (!timeMap.isEmpty()) {
            params.put(params_time, timeMap);
        }
    }

    /*private List<SurveyRespondentProp> getSeqByPropExt(JSONObject params, JSONObject ext) {
        LambdaQueryWrapper<SurveyRespondentProp> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SurveyRespondentProp::getProjectId, params.getString("project_id"));
        queryWrapper.eq(StrUtil.isNotEmpty(params.getString("seq")), SurveyRespondentProp::getSeq, params.getString("seq"));
        //$or：任意条件，$and：全部条件
        String ctype = ext.getString("ctype");
        List<JSONObject> plist = (List<JSONObject>) ext.get("v");
        //'open_data': {'$in': []}
        String orgId = SecurityContextHolder.getOrgId();
        plist.forEach(p -> {
            String name = "custom_data__" + p.getString("name");
            List<MemberDto> member_field_list = memberGrpcClient.ListMembersByParam(orgId, p.getInteger("mtype"), p.getString("dtype"));
            member_field_list.stream().map(v -> String.format("%s:%s", name, v)).collect(Collectors.toList());
        });
        return Collections.emptyList();
    }*/

    private List<SurveyRespondentPropData> getSeqByPropExtP(JSONObject params, Map ext_p) {
        LambdaQueryWrapper<SurveyRespondentPropData> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SurveyRespondentPropData::getProjectId, params.getString("project_id"));
        queryWrapper.eq(StrUtil.isNotEmpty(params.getString("seq")), SurveyRespondentPropData::getSeq, params.getString("seq"));
        //后续条件补充
        //for k, v_list in ext_p.items():
        //            props.append({"key": f"custom_data__{k}", "open_data": {'$in': v_list}})
        return surveyRespondentPropDataMapper.selectList(queryWrapper);
    }

    /**
     * 根据筛选条件筛选出符合条件的seq
     *
     * @param params    params = {project_id, version, status, source, group_id, seq?} project_id_7 discard?
     * @param qList
     * @param cond_list qlist = [{id: "2", op: "select", qtype: "single", v: [4]}]
     * @param qlogic    qlogic = 'and'
     * @param condlogic
     * @return
     */
    private List<Long> getSeqByProp(JSONObject params, List<Map> qList, List cond_list, String qlogic, String condlogic) {
        LambdaQueryWrapper<SurveyRespondentPropData> query = new LambdaQueryWrapper<>();
        query.select(SurveyRespondentPropData::getSeq);
        //此处根据 project_id 做分表处理
        qList.forEach(q -> {
            String q_id = (String) q.get("id");
            String op = (String) q.get("op");
            Object v = q.get("v");
            String qtype = (String) q.getOrDefault("qtype", "");
            if (StrUtil.isNotEmpty(q_id) && StrUtil.isNotEmpty(op) && (ObjectUtil.isNotEmpty(v)) || (v.equals(0))) {
                //0也需要
                if ("select".equals(op)) {
                    if (D_TYPE_6.contains(qtype)) {
                        if ("geo".equals(qtype)) {
                            String[] value = (String[]) v;
                            if (value.length > 1 && (value[0].equals(value[0] + "市"))) {
                                value[1] = value[0];
                            }
                            for (int i = 0; i < value.length; i++) {
                                String option = value[i];
                                query.eq(SurveyRespondentPropData::getQgid, q_id);
                                query.eq(SurveyRespondentPropData::getKey, String.valueOf(i + 1));
                                query.eq(SurveyRespondentPropData::getValue, option);
                            }
                        }
                    } else if ("evaluation".equals(qtype)) {
                        //新版评价题 {id: "6_tagsId", op: "select", qtype: "evaluation", v: [gid, gid], tagMap:{gid:好}}
                        JSONObject tagMap = (JSONObject) q.getOrDefault("tagMap", new JSONObject());
                        //拼接成老板的标签 v
                        v = ((List<String>) v).stream().map(gid -> tagMap.get(gid)).collect(Collectors.toList());
                        //拼接成老板的标签 q_id
                        q_id = q_id.replace("tagsId", "tag");
                        //旧版评价题 {id: "6_tag", op: "select", qtype: "evaluation", v: ["好", "很好"]}
                        query.eq(SurveyRespondentPropData::getQgid, q_id);
                        query.in(SurveyRespondentPropData::getValue, (List) v);
                    } else if ("score".equals(qtype)) {
                        //移除q_id末尾的"_tag"
                        q_id = q_id.endsWith("_tag") ? q_id.substring(0, q_id.length() - 4) : q_id;
                        if (q_id.contains("_")) {
                            String qi = q_id.substring(0, q_id.indexOf("_"));
                            String k = q_id.substring(q_id.indexOf("_") + 1);
                            query.eq(SurveyRespondentPropData::getQgid, qi);
                            query.in(SurveyRespondentPropData::getValue, (List) v);
                            query.eq(NumberUtils.isDigits(k) || k.contains("_"), SurveyRespondentPropData::getKey, k);
                        } else {
                            query.eq(SurveyRespondentPropData::getQgid, q_id);
                            query.in(SurveyRespondentPropData::getValue, (List) v);
                        }
                    } else {
                        if (q_id.contains("_")) {
                            String qi = q_id.substring(0, q_id.indexOf("_"));
                            String k = q_id.substring(q_id.indexOf("_") + 1);
                            query.eq(SurveyRespondentPropData::getQgid, qi);
                            query.in(SurveyRespondentPropData::getValue, (List) v);
                            query.eq(NumberUtils.isDigits(k) || k.contains("_"), SurveyRespondentPropData::getKey, k);
                        } else {
                            query.eq(SurveyRespondentPropData::getQgid, q_id);
                            query.in(SurveyRespondentPropData::getValue, (List) v);
                        }
                    }
                } else if ("co".equals(op)) {
                    if ("matrix_multiple".equals(qtype)) {
                        if (q_id.contains("_")) {
                            String qi = q_id.substring(0, q_id.indexOf("_"));
                            String k = q_id.substring(q_id.indexOf("_") + 1);
                            query.eq(SurveyRespondentPropData::getQgid, qi);
                            query.eq(SurveyRespondentPropData::getKey, k);
                            query.in(SurveyRespondentPropData::getValue, (List) v);
                        }
                    } else if ("evaluation".equals(qtype)) {
                        query.eq(SurveyRespondentPropData::getQgid, q_id.split("_")[0]);
                        query.like(SurveyRespondentPropData::getOpenData, v);
                    } else {
                        query.eq(SurveyRespondentPropData::getQgid, q_id);
                        query.like(SurveyRespondentPropData::getOpenData, v);
                    }
                } else if ("nc".equals(op)) {
                    if ("matrix_multiple".equals(qtype)) {
                        if (q_id.contains("_")) {
                            //此处子查询 query.notInSql() 也可以实现，不过无法实现防注入
                            String qi = q_id.substring(0, q_id.indexOf("_"));
                            String k = q_id.substring(q_id.indexOf("_") + 1);
                            LambdaQueryWrapper<SurveyRespondentPropData> subQuery = new LambdaQueryWrapper<>();
                            subQuery.select(SurveyRespondentPropData::getSeq);
                            subQuery.eq(SurveyRespondentPropData::getQgid, qi);
                            subQuery.eq(SurveyRespondentPropData::getKey, k);
                            subQuery.in(SurveyRespondentPropData::getValue, (List) v);
                            query.notIn(SurveyRespondentPropData::getSeq, surveyRespondentPropDataMapper.selectList(subQuery).stream().map(SurveyRespondentPropData::getSeq).collect(Collectors.toList()));
                        }
                    } else if ("evaluation".equals(qtype)) {
                        query.eq(SurveyRespondentPropData::getQgid, q_id.split("_")[0]);
                        query.notLike(SurveyRespondentPropData::getOpenData, v);
                    } else {
                        query.eq(SurveyRespondentPropData::getQgid, q_id);
                        query.notLike(SurveyRespondentPropData::getOpenData, v);
                    }
                } else {
                    query.eq(SurveyRespondentPropData::getQgid, q_id);
                    if (q_id.contains("_")) {
                        String qi = q_id.substring(0, q_id.indexOf("_"));
                        String k = q_id.substring(q_id.indexOf("_") + 1);
                        query.eq(SurveyRespondentPropData::getQgid, qi);
                        query.eq(NumberUtils.isDigits(k) || k.contains("_"), SurveyRespondentPropData::getKey, k);
                    } else if ("eq".equals(op)) {
                        if ((v instanceof Integer || v instanceof Float) || isSocre(qtype)) {
                            query.eq(SurveyRespondentPropData::getValue, v);
                        } else if ("matrix_multiple".equals(qtype)) {
                            query.in(SurveyRespondentPropData::getValue, (List) v);
                        } else {
                            if ("evaluation".equals(qtype)) {
                                //移除q_id末尾的"_open"
                                q_id = q_id.endsWith("_open") ? q_id.substring(0, q_id.length() - 5) : q_id;
                                query.eq(SurveyRespondentPropData::getQgid, q_id);
                                query.eq(SurveyRespondentPropData::getOpenData, v);
                            } else if ("timestamp".equals(qtype)) {
                                Object finalV = v;
                                query.and(qe -> {
                                    qe.eq(SurveyRespondentPropData::getKey, "tag");
                                    qe.or(qw -> {
                                        qw.eq(SurveyRespondentPropData::getOpenData, finalV);
                                        qw.eq(SurveyRespondentPropData::getValue, finalV);
                                    });
                                });
                            } else {
                                query.eq(SurveyRespondentPropData::getOpenData, v);
                            }
                        }
                    } else if ("ne".equals(op)) {
                        if ((v instanceof Integer || v instanceof Float) || isSocre(qtype)) {
                            query.ne(SurveyRespondentPropData::getValue, v);
                        } else if ("matrix_multiple".equals(qtype)) {
                            query.notIn(SurveyRespondentPropData::getValue, v);
                        } else if ("evaluation".equals(qtype)) {
                            //移除q_id末尾的"_open"
                            q_id = q_id.endsWith("_open") ? q_id.substring(0, q_id.length() - 5) : q_id;
                            String finalQ_id = q_id;
                            Object finalV1 = v;
                            query.or(qe -> {
                                query.eq(SurveyRespondentPropData::getQgid, finalQ_id);
                                query.ne(SurveyRespondentPropData::getOpenData, finalV1);
                            });
                            query.or(qe -> {
                                query.eq(SurveyRespondentPropData::getQgid, finalQ_id);
                                query.eq(SurveyRespondentPropData::getOpenData, null);
                            });
                        } else {
                            if (!"range".equals(qtype)) {
                                //处理gt/gte/lt/lte
                                query.eq(SurveyRespondentPropData::getValue, String.format("$%s", v));
                                query.ne(SurveyRespondentPropData::getKey, "tag");
                            } else {
                                Object left = ((List) v).get(0);
                                Object right = ((List) v).get(1);
                                //true为$gt，false为$gte
                                boolean l = (boolean) q.get("leftOpen");
                                //true为$lt，false为$lte
                                boolean r = (boolean) q.get("rightOpen");
                                if ("timedelta".equals(qtype)) {
                                    query.and(qe -> {
                                        qe.ne(SurveyRespondentPropData::getKey, "tag");
                                        if (l) {
                                            qe.or(qw -> {
                                                qw.gt(SurveyRespondentPropData::getOpenData, left);
                                                qw.gt(SurveyRespondentPropData::getValue, left);
                                            });
                                        } else {
                                            qe.or(qw -> {
                                                qw.ge(SurveyRespondentPropData::getOpenData, right);
                                                qw.ge(SurveyRespondentPropData::getValue, right);
                                            });
                                        }
                                        if (r) {
                                            qe.or(qw -> {
                                                qw.lt(SurveyRespondentPropData::getOpenData, left);
                                                qw.lt(SurveyRespondentPropData::getValue, left);
                                            });
                                        } else {
                                            qe.or(qw -> {
                                                qw.le(SurveyRespondentPropData::getOpenData, right);
                                                qw.le(SurveyRespondentPropData::getValue, right);
                                            });
                                        }
                                    });
                                } else {
                                    query.and(qe -> {
                                        qe.ne(SurveyRespondentPropData::getKey, "tag");
                                        if (l) {
                                            qe.or(qw -> qw.gt(SurveyRespondentPropData::getValue, left));
                                        } else {
                                            qe.or(qw -> qw.ge(SurveyRespondentPropData::getValue, right));
                                        }
                                        if (r) {
                                            qe.or(qw -> qw.lt(SurveyRespondentPropData::getValue, left));
                                        } else {
                                            qe.or(qw -> qw.le(SurveyRespondentPropData::getValue, right));
                                        }
                                    });
                                }
                            }
                        }
                    }
                }
            }
        });
        if ("and".equals(qlogic)) {
            query.and(qw -> baseCondition(qw, params));
        } else {
            query.or(qw -> baseCondition(qw, params));
        }
        return surveyRespondentPropDataMapper.selectList(query).stream().map(SurveyRespondentPropData::getSeq).collect(Collectors.toList());
    }

    private void baseCondition(LambdaQueryWrapper<SurveyRespondentPropData> query, JSONObject params) {
        query.eq(SurveyRespondentPropData::getProjectId, params.getString("project_id"));
        query.in(SurveyRespondentPropData::getStatus, params.getJSONArray("status"));
        String seq = (String) params.remove("seq");
        query.eq(StrUtil.isNotEmpty(seq), SurveyRespondentPropData::getSeq, seq);
    }

    private boolean isSocre(String qtype) {
        return D_TYPE_7.contains(qtype) || qtype.contains("score");
    }

    /**
     * 取层级下所有group_id
     *
     * @param data
     * @param group_ids
     */
    private void getGroupIds(GroupNode data, List group_ids) {
        group_ids.add(data.getId());
        Optional.ofNullable(data.getChildrenList()).orElse(Collections.emptyList()).forEach(v -> getGroupIds(v, group_ids));
    }


    private JSONObject doId(JSONObject params, String id, String project_id) {
        params.put("id", id);
        LambdaQueryWrapper<SurveyRespondent> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SurveyRespondent::getProjectId, project_id);
        queryWrapper.eq(StrUtil.isNotEmpty(project_id), SurveyRespondent::getProjectId, project_id);
        SurveyRespondent respondent = surveyRespondentMapper.selectOne(queryWrapper);
        if (Objects.isNull(respondent)) {
            throw new BusinessException("Respondent[id={" + id + "}] not found");
        }
        if (StrUtil.isEmpty(project_id)) {
            project_id = respondent.getProjectId();
        }
        Project project = qdesGrpcClient.getProject(project_id);
        if (Objects.isNull(project)) {
            throw new BusinessException("Project[id={" + id + "}] not found");
        }
        String orgId = SecurityContextHolder.getOrgId();
        checkOrg(project.getCompanyId(), orgId);
        JSONObject surveyRespondent = JSONObject.parseObject(JSON.toJSONString(respondent));
        if (ObjectUtil.isNotEmpty(respondent.getDeliverId())) {
            DeliverDto deliver = deliverGrpcClient.getDeliver(respondent.getDeliverId());
            surveyRespondent.put("deliver_name", deliver.getName());
            if (ObjectUtil.isNotEmpty(params.get("withProject"))) {
                //联系人查看历史答卷只需要带出项目和问卷标题
                surveyRespondent.put("project_title", project.getTitle());
                surveyRespondent.put("survey_title", surveyColumnService.getsurveytitle(respondent.getProjectId(), respondent.getVersion()));
            }
        }
        return surveyRespondent;
    }

    private List<JSONObject> getSortedTagList(JSONObject data) {
        List<JSONObject> tag_list = (List<JSONObject>) ((Map) data.getJSONObject("current_template").getOrDefault("tagList", Collections.emptyMap())).values().stream().collect(Collectors.toList());
        tag_list.sort((a, b) -> {
            String[] a1 = ((List<String>) a.get("ooid")).get(0).split("_");
            String[] b1 = ((List<String>) b.get("ooid")).get(0).split("_");
            int value = a1[0].compareTo(b1[0]);
            if (value == 0) {
                return a1[1].compareTo(b1[1]);
            }
            return value;
        });
        return tag_list;
    }
}